{"posts":[{"content":"A C909 scanf(&quot;%s&quot;,a)与gets(a) printf(&quot;%s&quot;,a)与puts(a) 代码 #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;string.h&gt; #include &lt;math.h&gt; char s[20][200]={ &quot;The country's first home-developed regional passenger jet, the ARJ21,\\n&quot;, &quot;has added a new branding name C909,\\n&quot;, &quot;Shanghai-based Commercial Aircraft Corp of China said on\\n&quot;, &quot;Tuesday during the 15th China International Aviation\\n&quot;, &quot;and Aerospace Exhibition,\\n&quot;, &quot;also known as the Zhuhai Airshow, in Zhuhai, Guangdong province.\\n&quot;, &quot;A total of 150 C909 jets have been delivered to different carriers\\n&quot;, &quot;since its commercial launch in 2016 and safely carried over 17 million passenger trips.\\n&quot;, &quot;The aircraft manufacturer COMAC said\\n&quot;, &quot;it has continued to enhance the performance,\\n&quot;, &quot;crew operation experience,\\n&quot;, &quot;and cabin comfort of the C909 aircraft.\\n&quot;}; int main() { int l,r; scanf(&quot;%d%d&quot;,&amp;l,&amp;r); for (int i = l; i &lt;=r ; ++i) { printf(&quot;%s&quot;,s[i-1]); } } B 小宇的字符查找 涉及数字与字符的混合输入 清楚理解scanf(&quot;%s&quot;)和gets()的区别（读入前、终止条件、读入后） 清楚理解读取数字的scanf(&quot;%d&quot;) %lld,%lf在读入前、终止条件、读入后的行为 scanf(&quot;%s&quot;)和gets() 读入前 中止条件 读入后 scanf(&quot;%s&quot;) 会忽略\\n和空格 遇到空格或\\n 空格或\\n会留在缓冲区内 gets() 不会忽略\\n 遇到\\n 把\\n读入并转换为0作为字符串结束符 scanf(&quot;%c&quot;)与getchar() 在字符读入上完全等价，只是用法和返回值不同。 scanf(&quot;%c&quot;,&amp;a); a=getchar(); 123a 1234 %d等数字读入 %d、%lld、%lf等数字读入 读入前 中止条件 读入后 %d、%lld、%lf 忽略所有空白符 遇到非数字字符 不作处理 如果在读入前遇到了非数字字符，直接读入失败，不进行读入和赋值 何时需要使用getchar() 当先用%d读入一个数字，然后接着用gets()或getchar()、%c去读下一行的字符或字符串。 此时%d只会读数字，而\\n会留下来。 因此需要getchar()去把\\n吃掉，方便后续使用gets()或getchar()、%c去读下一行的字符或字符串。 实例 123 123 123 abcd cdef #include &lt;stdio.h&gt; int main() { int a,b,c,d; double e,f,g; char str1[100]; char str2[100]; char str3[100]; scanf(&quot;%d%d%d&quot;,&amp;a,&amp;b,&amp;c); //scanf(&quot;%s&quot;,str1); //gets(str2); //gets(str3); return 0; } strchr(s, c)、strrchr(s, c) strchr()和strrchr() 1.函数声明： char *strchr(const char *s,int c); char *strrchr(const char *s,int c); 2.功能：若字符串s中存在字符c，strchr()和strrchr()分别返回字符c在s中第一次和最后一次出现的位置的指针。 若字符串s中没有字符c，则返回NULL 代码 #include &lt;stdio.h&gt; #include &lt;string.h&gt; int main() { char s[1005]; gets(s); char c; while (scanf(&quot;%c&quot;, &amp;c) != EOF) { getchar(); if (strchr(s, c) != NULL) { int first = strchr(s, c) - s; int last = strrchr(s, c) - s; printf(&quot;%d %d\\n&quot;, first, last); } else { printf(&quot;-1\\n&quot;); } } } #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;string.h&gt; #include &lt;math.h&gt; char s[1010]; int wei[1010]; int main(){ gets(s); int len=strlen(s); char a; int i,j; int flag=0; while(scanf(&quot;%c&quot;,&amp;a)!=EOF) { flag=0; for(i=0;i&lt;len;i++) { if(a==s[i]) { flag=1; printf(&quot;%d &quot;,i); break; } } for(j=len-1;j&gt;=0;j--) { if(a==s[j]) { flag=1; printf(&quot;%d\\n&quot;,j); break; } } if(flag==0) { printf(&quot;-1\\n&quot;); } getchar(); } return 0; } C 翻转加密 #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;string.h&gt; #include &lt;math.h&gt; int main(){ char s[105]; gets(s); int a,b; scanf(&quot;%d%d&quot;,&amp;a,&amp;b); while (a&lt;b)//不能写a!=b { char temp=s[a]; s[a]=s[b]; s[b]=temp; a++;b--; } puts(s); } D 循环子序列 代码 #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;string.h&gt; #include &lt;math.h&gt; char s0[100005]; char s1[100005]; char s[200005]; int flag[200005]; int main() { gets(s0); gets(s1); int s0len=strlen(s0); int s1len=strlen(s1); for (int i = 0; i &lt; s0len; ++i) { s[i]=s0[i]; } for (int i = 0; i &lt; s0len; ++i) { s[i+s0len]=s0[i]; } s[s0len*2]=0; char *p=s; while ((p=strstr(p,s1))!=NULL) { int start=p-s; for (int i = start; i &lt;s1len+start ; ++i) { flag[i]=1; } p+=s1len; } for (int i = 0; i &lt; s0len; ++i) { if (flag[i]==1||flag[i+s0len]==1) printf(&quot;%c&quot;,s0[i]); else printf(&quot;*&quot;); } } E 帕林卓姆 样例1 #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;string.h&gt; #include &lt;math.h&gt; int main(){ char s[1005]; while (gets(s)!=NULL) { int len= strlen(s); int flag=0; for (int i = 0; i &lt; len; ++i) { if (s[i]!=s[len-1-i]) { printf(&quot;NO\\n&quot;); flag=1; break; } } if (flag==0) printf(&quot;YES\\n&quot;); } } 样例2 #include &lt;stdio.h&gt; #include &lt;string.h&gt; int main() { char s[1005]; while(~scanf(&quot;%s&quot;, s)) { char res[1005]={0}; int len = strlen(s); for(int i = 0; i &lt; len; i++) { res[i] = s[len-i-1]; } if (strcmp(s, res)) printf(&quot;NO\\n&quot;); else printf(&quot;YES\\n&quot;); } return 0; } 代码 #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;string.h&gt; #include &lt;math.h&gt; int find(char s[]) { char *p=s-1; int num=0; while (p!=NULL) { p++;num++; p=strstr(p,&quot;CVBB&quot;); } num--; return num; } int main(){ int q; char s[1005]; gets(s); scanf(&quot;%d&quot;,&amp;q); for (int i = 0; i &lt; q; ++i) { int x;char c; scanf(&quot;%d %c&quot;,&amp;x,&amp;c); s[x-1]=c; printf(&quot;%d\\n&quot;,find(s)); } } 字符串输出函数 puts() fputs() printf() sprintf() (字符串构造) 字符串输入函数 scanf() gets() fgets() 字符串复制 strcpy() strncpy() 字符串追加函数 strcat() strncat() 字符串比较函数 strcmp() strncmp() 字符串检查函数 strlen()长度 strchr() strrchr() 查找字符位置 strstr() 查找子字符串位置 字符串扫描函数 sscanf() ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C7讲解-24航C","link":"https://github.pansis.site/post/C7讲解-24航C/","stats":{"text":"8 min read","time":436000,"words":1362,"minutes":8},"dateFormat":"2024-11-18"},{"content":"A C909 难度 考点 1 二维字符数组 题目分析 将一行英文存入二维数组中的某一行，共 121212 行，然后循环输出即可。 示例代码 #include &lt;stdio.h&gt; char s[20][200]={ &quot;The country's first home-developed regional passenger jet, the ARJ21,\\n&quot;, &quot;has added a new branding name C909,\\n&quot;, &quot;Shanghai-based Commercial Aircraft Corp of China said on\\n&quot;, &quot;Tuesday during the 15th China International Aviation\\n&quot;, &quot;and Aerospace Exhibition,\\n&quot;, &quot;also known as the Zhuhai Airshow, in Zhuhai, Guangdong province.\\n&quot;, &quot;A total of 150 C909 jets have been delivered to different carriers\\n&quot;, &quot;since its commercial launch in 2016 and safely carried over 17 million passenger trips.\\n&quot;, &quot;The aircraft manufacturer COMAC said\\n&quot;, &quot;it has continued to enhance the performance,\\n&quot;, &quot;crew operation experience,\\n&quot;, &quot;and cabin comfort of the C909 aircraft.\\n&quot;}; int main(){ int l,r; scanf(&quot;%d%d&quot;,&amp;l,&amp;r); for(int i=l;i&lt;=r;i++){ printf(&quot;%s&quot;,s[i-1]); } return 0; } B 小宇的字符查找 难度 考点 2 字符串函数 指针 题目分析 这道题的难点在于输入和字符查找两部分。 输入问题 首先需要输入一个字符串，由于输入的字符串包含空格，因此需要使用 gets() 而非 scanf() （前者遇到回车停止，后者遇到空格或回车停止）。 接下来涉及到不定组输入，因此需要使用 while(scanf(&quot;%c&quot;,&amp;a)!=EOF) ，需要注意的是每一行除了字符 aaa 以外，还有回车。需要在循环内部用 getchar() 读取掉，否则在下次循环中 while(scanf(&quot;%c&quot;,&amp;a)!=EOF) 读取的是回车而非下一行的字符 aaa。 字符查找 字符查找有两种方式，一种是用字符串查找函数 strchr() 和 strrchr() 查找第一次和最后一次出现的字符，另一种是用两个循环去分别查找第一次和最后一次出现的字符。这两种方法分别对应示例代码1和示例代码2。 需要注意的是，strchr(s,c) 和 strrchr(s,c) 返回的是字符 ccc 在 sss 中第一次或最后一次出现的位置的指针，并不是题目所要求的的数组下标，需要对返回值减掉数组第0个元素的指针（即 s）后方可得到数组下标。 示例代码 - 1 #include &lt;stdio.h&gt; #include &lt;string.h&gt; int main() { char s[1005]; gets(s); char c; while (scanf(&quot;%c&quot;, &amp;c) != EOF) { getchar(); if (strchr(s, c) != NULL) { int first = strchr(s, c) - s; int last = strrchr(s, c) - s; printf(&quot;%d %d\\n&quot;, first, last); } else { printf(&quot;-1\\n&quot;); } } } 示例代码 - 2 #include &lt;stdio.h&gt; #include &lt;string.h&gt; int main() { char s[1010]; gets(s); int len = strlen(s); char a; while (scanf(&quot;%c&quot;, &amp;a) != EOF) { int flag = 0; for (int i = 0; i &lt; len; i++)//从前往后找第一个出现的 { if (a == s[i]) { flag = 1;//找到了 printf(&quot;%d &quot;, i); break; } } for (int j = len - 1; j &gt;= 0; j--)//从后往前找最后一个出现的 { if (a == s[j]) { flag = 1;//找到了 printf(&quot;%d\\n&quot;, j); break; } } if (flag == 0)//如果没有找到 { printf(&quot;-1\\n&quot;); } getchar(); } return 0; } C 翻转加密 难度 考点 2 字符串 题目分析 对于翻转的操作，我们可以用之前学过的方式交换 s[a] 和 s[b] 的值，然后将 a 的值加一，b 的值减一，重复这样的交换操作，直到交换完所有字符，即完成了子串的翻转。 示例代码 #include &lt;stdio.h&gt; int main() { char s[105]; int a, b; scanf(&quot;%s&quot;, s); scanf(&quot;%d%d&quot;, &amp;a, &amp;b); while (a &lt; b) { char temp = s[a]; s[a] = s[b]; s[b] = temp; a++; b--; } printf(&quot;%s&quot;, s); return 0; } D 帕林卓姆 难度 考点 2 字符串遍历、回文 题目分析 如何不定组输入字符串呢？其实就是 while(scanf(&quot;%s&quot;, str) != EOF)，也可以写为 while(gets(str) != NULL)。这是因为，当 scanf 读取到文件或者输入末尾时，返回的是 EOF。gets 读取到文件或者输入末尾时，返回的是 NULL。 每次读取一行字符串后，使用 strlen 获取字符串的长度，然后我们从字符串开头和结尾开始遍历，每次比较两个字符是否相等。如果不等，我们进行标记，然后直接退出遍历即可。这是因为，只要存在一处不等字符，该字符串就不是回文串了。遍历结束后，根据标记变量的结果进行输出即可。 示例代码还可以做出改进，我们并不需要从 0 遍历到 len-1，遍历到字符串的一半即可。根据字符串长度的奇偶性，i 和 len - 1 - i 可能存在重叠，可能不存在，但要实现遍历一半的效果，保证 i &lt; len - 1 - i 即可。 示例代码 #include &lt;stdio.h&gt; #include &lt;string.h&gt; char str[1010]; int main() { while (gets(str) != NULL) { int len = strlen(str); int flag = 1; for (int i = 0; i &lt; len; ++i) { if (str[i] != str[len - 1 - i]) { flag = 0; break; } } if(flag == 1)puts(&quot;YES&quot;); else puts(&quot;NO&quot;); //puts(flag ? &quot;YES&quot; : &quot;NO&quot;); } return 0; } E 查询CVBB 弱化版 难度 考点 3 字符串 题目分析 按题目的要求修改并查询 CVBB 的个数即可。 查询 CVBB 的个数时可以用循环每个字符都比对一遍，也可以使用 strstr 函数。 示例代码 - 1 #include &lt;stdio.h&gt; char s[1010]; int q; int calc_cvbb() { int ans=0; int n=strlen(s+1); for(int i=1;i&lt;=n-3;i++) { if(s[i]=='C'&amp;&amp;s[i+1]=='V'&amp;&amp;s[i+2]=='B'&amp;&amp;s[i+3]=='B') ans++; } return ans; } int main() { scanf(&quot;%s&quot;,s+1); scanf(&quot;%d&quot;,&amp;q); for(int i=1;i&lt;=q;i++) { int x; char c; scanf(&quot;%d %c&quot;,&amp;x,&amp;c); s[x]=c; printf(&quot;%d\\n&quot;,calc_cvbb()); } return 0; } 示例代码 - 2 #include &lt;stdio.h&gt; char s[1010]; int q; int calc_cvbb() { char *p=s+1; int ans=0; int len=strlen(s+1); while(p&lt;=s+len-1&amp;&amp;((p=strstr(p,&quot;CVBB&quot;))!=NULL)) { ans++; p++; } return ans; } int main() { scanf(&quot;%s&quot;,s+1); scanf(&quot;%d&quot;,&amp;q); for(int i=1;i&lt;=q;i++) { int x; char c; scanf(&quot;%d %c&quot;,&amp;x,&amp;c); s[x]=c; printf(&quot;%d\\n&quot;,calc_cvbb()); } return 0; } F 循环子序列 难度 考点 3 指针，strstr 题目分析 我们可以将 s0s_0s0​ 复制一份接到后面，再在新的字符串上进行查找，即可查询到所有循环子序列的位置了。 示例代码 #include &lt;stdio.h&gt; #include &lt;string.h&gt; char s0[200005]; int flag[200006]; char s1[100005]; int main() { scanf(&quot;%s&quot;, s0); scanf(&quot;%s&quot;, s1); int len0 = (int)strlen(s0); int len1 = (int)strlen(s1); // 注意，strcat函数的两个参数不能是同一个字符串 for (int i = 0; i &lt; len1; i++) { // 复制一份接到后面 s0[i + len0] = s0[i]; } char *p = s0; while (strstr(p, s1) != NULL) { // 查询所有s1子序列 p = strstr(p, s1); for (int i = p - s0; i &lt; p - s0 + len1; i++) { // 标记子序列位置 flag[i] = 1; } p = p + len1; } for (int i = 0; i &lt; len0; i++) { if (flag[i] || flag[i + len0]) {// 前半和后半部分有标记都算是循环子序列 printf(&quot;%c&quot;, s0[i]); }else { printf(&quot;*&quot;); } } return 0; } G 欢唱，以我之名！ 难度 考点 3 字符串操作 题目分析 本题的操作过程大致可以分为几个步骤： 判断 s1s_1s1​ 结尾和 s2s_2s2​ 开头字符是否相同。如果相同，进行下一步，如果不相同，则结束操作； 计算 s1s_1s1​ 结尾和 s2s_2s2​ 开头相同字符总数，若大于等于 333 进行下一步，否则结束操作； 在 s1s_1s1​ 和 s2s_2s2​ 结尾分别删去若干字符。返回第一步判断。 对于第三步，我们知道字符串是字符类型的数组，数组的名称可以直接作为指向首元素的指针。因此， 如果想要在字符串char str[]中删除前n个字符，只需要运用指针的运算，让str = str + n即可。 如果想要在char str[]中删除后n个字符，根据字符串的特性，可以将倒数第n个元素赋值为'\\0'，即str[strlen(str) - n] = '\\0' 为直观起见，我们将上述的步骤包装成函数，具体见示例代码。 示例代码 #include &lt;stdio.h&gt; #include &lt;string.h&gt; #include &lt;stdlib.h&gt; char lastChar(char *s) { return strlen(s) ? s[strlen(s) - 1] : 0; } int lastNum(char *s) { char tmp = lastChar(s); int i = strlen(s), cnt = 0; while (i--) { if (s[i] == tmp) { cnt++; continue; } break; } return cnt; } int firstNum(char *s) { int i = 1; while (s[i++] == s[0]) ; return --i; } int main() { int n; scanf(&quot;%d&quot;, &amp;n); char *s1 = (char *)malloc(sizeof(char) * 1005); char *s2 = (char *)malloc(sizeof(char) * 1005); char *STATIC_S2 = s2; while (n--) { scanf(&quot;%s&quot;, s1); getchar(); scanf(&quot;%s&quot;, s2); getchar(); int s1_l = strlen(s1); int s2_l = strlen(s2); int s1_n = lastNum(s1); int s2_n = firstNum(s2); while (s1_l &gt; 0 &amp;&amp; s2_l &gt; 0 &amp;&amp; lastChar(s1) == s2[0] &amp;&amp; s1_n + s2_n &gt;= 3) { s1[s1_l - s1_n] = '\\0'; s2 = s2 + s2_n; s1_l = strlen(s1); s2_l = strlen(s2); s1_n = lastNum(s1); s2_n = firstNum(s2); } if (s1_l == 0 &amp;&amp; s2_l == 0) { printf(&quot;clear!\\n&quot;); } else if (s1_l != 0 &amp;&amp; s2_l != 0) { printf(&quot;%s%s\\n&quot;, s1, s2); } else if (s1_l == 0) { printf(&quot;kori!\\n&quot;); } else printf(&quot;miracle!\\n&quot;); s2 = STATIC_S2; } return 0; } H 五字不行 难度 考点 4 深度优先搜索，二维数组 题目分析 对于每次询问，假设子矩阵大小为 p×qp\\times qp×q，我们可以考虑一个同样为 p×qp\\times qp×q 的空数组。从考察矩阵中的任一元素出发，将其周围所有和它相等的元素在新的空数组中标记出来，如果标记的数量大于 555 则停止搜索，直接输出；若否，则继续搜索未标记过的位置，并重新判断。如果一直没有超过 555 个标记，则输出No 搜索策略：深度优先搜索 D‾epthF‾irstS‾earch\\underline{D}epth \\underline{F}irst \\underline{S}earchD​epthF​irstS​earch 从数组中一个元素开始，向四个邻接方向搜索。如果邻接元素符合要求，则将这个元素作为搜索起始元素；如果四个邻接方向都没有，则说明该元素是连续元素的边界，回溯到上一级。 此处的要求是：搜索的元素依然在子矩阵范围内；搜索的元素和原来的元素值相等；搜索的元素没有被搜索过。 示例代码 #include &lt;stdio.h&gt; #include &lt;stdbool.h&gt; #include &lt;string.h&gt; int p = 1, map[105][105], arr[105][105], tgt; const int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; bool check(int startx, int starty, int dx, int dy, int x, int y, int tgt); void dfs(int startx, int starty, int dx, int dy, int x, int y); int main() { int n, m, t, x, y, a, b; scanf(&quot;%d %d&quot;, &amp;m, &amp;n); for (int i = 0; i &lt; m; i++) { for (int j = 0; j &lt; n; j++) { scanf(&quot;%d&quot;, &amp;arr[i][j]); } } scanf(&quot;%d&quot;, &amp;t); while (t--) { memset(map[0], 0, 105 * 105 * sizeof(int)); scanf(&quot;%d %d %d %d&quot;, &amp;y, &amp;x, &amp;a, &amp;b); for (int i = 0; i &lt; a &amp;&amp; p &lt; 5; i++) { for (int j = 0; j &lt; b &amp;&amp; p &lt; 5; j++) { if (map[y + i][x + j] == 0) { tgt = arr[y + i][x + j]; map[y + i][x + j] = 1; dfs(x, y, b, a, x + j, y + i); if (p &gt; 4) puts(&quot;Yes&quot;); else p = 1; } } } if (p &lt; 5) puts(&quot;No&quot;); p = 1; } return 0; } bool check(int startx, int starty, int dx, int dy, int x, int y, int tgt) { if (x &lt; (startx + dx) &amp;&amp; x &gt;= startx &amp;&amp; y &lt; starty + dy &amp;&amp; y &gt;= starty &amp;&amp; map[y][x] != 1 &amp;&amp; arr[y][x] == tgt) { return 1; } return 0; } void dfs(int startx, int starty, int dx, int dy, int x, int y) { for (int i = 0; i &lt; 4; i++) { if (check(startx, starty, dx, dy, x + dir[i][0], y + dir[i][1], tgt)) { map[y + dir[i][1]][x + dir[i][0]] = 1; p += 1; dfs(startx, starty, dx, dy, x + dir[i][0], y + dir[i][1]); } } return; } I 字符串与子串 难度 考点 5 哈希思想，二分 or 双指针，动态数组 or 前缀和 题目分析 双指针做法的思路 我们可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针，一个用于「延伸」现有窗口的 r 指针，和一个用于「收缩」窗口的 l指针。在任意时刻，只有一个指针运动，而另一个保持静止。我们在 s 上滑动窗口，通过移动 r 指针不断扩张窗口。当窗口包含 t 全部所需的字符后，如果能收缩，我们就收缩窗口直到得到最小窗口。然后我们可以继续移动 l 指针，寻找下一处。 对于求解 l r 区间内的各个字母的数量，我们可以维护一个动态数组，记录各个字母的数量。也可以采用类似 前缀和 的方法，设 ss[i+1][j] 为到字符串的第 i 项统计的j 号字母数，则 ss[r+1][j]-ss[l][j] 即为 l r 区间内的 j 号字母的数量。 令 m=len(s),n=len(t)m=\\text{len}(s),n=\\text{len}(t)m=len(s),n=len(t) ，这种做法的时间复杂度为 O(m+n)O(m+n)O(m+n) 。 二分做法的思路 我们按照前缀和的方式统计完之后，我们可以假设最短子串的起点是第 iii 个字符，我们可以发现，如果第 iii 个字符到第 jjj 个字符的区间成立，那么到第 j+1,j+2⋯j+1,j+2\\cdotsj+1,j+2⋯ 个字符的区间都成立。我们设一个二元函数 check(i,j) 表示第 iii 个字符到第 jjj 个字符的区间是否成立，不成立为 000 ，成立为 111 。我们不难发现这个函数具有单调性，因此可以二分查找函数由 000 到 111 的转折点。 令 m=len(s),n=len(t)m=\\text{len}(s),n=\\text{len}(t)m=len(s),n=len(t) ，这种做法的时间复杂度为 O(mlog⁡m+n)O(m\\log{m} + n)O(mlogm+n) 。 示例代码 - 1 （双指针做法结合动态数组） #include&lt;stdio.h&gt; #include&lt;string.h&gt; int ss[30], tt[30]; int check() { for (int i = 0; i &lt;= 25; i++) { if (ss[i] &lt; tt[i]) return 0; } return 1; } char s[100005], t[100005]; int main() { while (scanf(&quot;%s%s&quot;, s, t) != EOF) { for (int i = 0; i &lt;= 25; i++) { ss[i] = 0; tt[i] = 0; }//注意数据的初始化 int lent = strlen(t); int lens = strlen(s); for (int i = 0; i &lt;= lent - 1; i++) { tt[t[i] - 'a']++; } int l = 0, r = 0, ansl = 0, ansr = 100000000; ss[s[0] - 'a']++; while (l &lt; lens || r &lt; lens) { if (check() == 0 &amp;&amp; r &lt; lens) { r++; if (r &lt; lens)ss[s[r] - 'a']++; } else if (check() == 1) { if (r - l &lt; ansr - ansl) { ansr = r; ansl = l; } l++; if (l &lt; lens)ss[s[l - 1] - 'a']--; } else { l++; if (l &lt; lens)ss[s[l - 1] - 'a']--; } } if (ansr == 100000000) { printf(&quot;000000\\n&quot;); } else { s[ansr + 1] = 0; printf(&quot;%s\\n&quot;, s + ansl); } } return 0; } 示例代码 - 2 （双指针做法结合前缀和） #include&lt;stdio.h&gt; #include&lt;string.h&gt; int ss[100005][30], tt[30]; int check(int st, int ed) { for (int i = 0; i &lt;= 25; i++) { if (ss[ed + 1][i] - ss[st][i] &lt; tt[i]) return 0; } return 1; } char s[100005], t[100005]; int main() { while (scanf(&quot;%s%s&quot;, s, t) != EOF) { for (int i = 0; i &lt;= 25; i++) { ss[0][i] = 0; tt[i] = 0; }//注意数据的初始化 int lent = strlen(t); int lens = strlen(s); for (int i = 0; i &lt;= lent - 1; i++) { tt[t[i] - 'a']++; } for (int i = 0; i &lt;= lens - 1; i++) { for (int j = 0; j &lt;= 25; j++) { ss[i + 1][j] = ss[i][j]; } ss[i + 1][s[i] - 'a']++; } int l = 0, r = 0, ansl = 0, ansr = 100000000; while (l &lt; lens || r &lt; lens) { if (check(l, r) == 0 &amp;&amp; r &lt; lens) { r++; } else if (check(l, r) == 1) { if (r - l &lt; ansr - ansl) { ansr = r; ansl = l; } l++; } else { l++; } } if (ansr == 100000000) { printf(&quot;000000\\n&quot;); } else { s[ansr + 1] = 0; printf(&quot;%s\\n&quot;, s + ansl); } } return 0; } 示例代码 - 3 （二分做法结合前缀和） #include&lt;stdio.h&gt; #include&lt;string.h&gt; int ss[100005][30], tt[30]; int check(int st, int ed) { for (int i = 0; i &lt;= 25; i++) { if (ss[ed + 1][i] - ss[st][i] &lt; tt[i]) return 0; } return 1; } char s[100005], t[100005]; int main() { while (scanf(&quot;%s%s&quot;, s, t) != EOF) { for (int i = 0; i &lt;= 25; i++) { ss[0][i] = 0; tt[i] = 0; }//注意数据的初始化 int lent = strlen(t); int lens = strlen(s); for (int i = 0; i &lt;= lent - 1; i++) { tt[t[i] - 'a']++; } for (int i = 0; i &lt;= lens - 1; i++) { for (int j = 0; j &lt;= 25; j++) { ss[i + 1][j] = ss[i][j]; } ss[i + 1][s[i] - 'a']++; } int ansl, ansr, anslen = 100000000;//anslen定义一个很大的非法值 for (int mid = 0; mid &lt;= lens - 1; mid++) { int nl = mid, nr = lens - 1; while (nr - nl &gt; 1) { int nmid = (nr + nl) / 2; if (check(mid, nmid)) { nr = nmid; if (anslen &gt; nmid - mid + 1) { anslen = nmid - mid + 1; ansl = mid; ansr = nmid; } } else { nl = nmid; } } if (check(mid, nr)) { if (anslen &gt; nr - mid + 1) { anslen = nr - mid + 1; ansl = mid; ansr = nr; } } if (check(mid, nl)) { if (anslen &gt; nl - mid + 1) { anslen = nl - mid + 1; ansl = mid; ansr = nl; } } } if (anslen != 100000000) { *(s + ansr + 1) = 0; printf(&quot;%s\\n&quot;, s + ansl); } else { printf(&quot;000000\\n&quot;); } } return 0; } J int 2 long long（仅供参考） 难度 考点 6 字符串、C语言语法/编译器模拟 题目分析 按照语法规则，排除掉字符串、变量名、函数名、注释，再进行替换即可。 字符串是放在两个英文双引号里的片段； 排除掉字符串，如果字符串 int 两边出现了 数字、其他字母、下划线，则是变量名/函数名。 如果是在某行的字符 // 后面，或者在/**/ 里面，则为注释。 其他的为关键字。（可能有考虑不全的情况，欢迎提出） 参考代码 本代码未必考虑了所有情况，仅考虑了 int 这三个字符在字符串、变量名、函数名、注释中的出现情况。 如果你找到了更多的情况（能使这份代码出现bug，且编译器没有报出警告），欢迎联系助教。 #include&lt;stdio.h&gt; #include&lt;string.h&gt; #include&lt;ctype.h&gt; #include&lt;stdlib.h&gt; char a[1010]; int check(int c) { if (c &lt; 0)return 0; if (isdigit(a[c]))return 1; if (a[c] == '_')return 1; if (isalpha(a[c]))return 1; return 0; } int main() { int sgn = 0; while (gets(a) != NULL) { int len = strlen(a); for (int i = 0; i &lt;= len - 1; i++) { if (sgn == 4) { if (a[i] == '*' &amp;&amp; a[i + 1] == '/') { sgn = 0; } } if (a[i] == '\\&quot;' &amp;&amp; (i == 0 || a[i - 1] != '\\\\')) { if (sgn == 0) { sgn = 1; } else if (sgn == 1) { sgn = 0; } } if (a[i] == '\\'' &amp;&amp; (i == 0 || a[i - 1] != '\\\\')) { if (sgn == 0) { sgn = 2; } else if (sgn == 2) { sgn = 0; } } if (sgn == 0) { if (a[i] == '/' &amp;&amp; a[i + 1] == '/') { sgn = 3; } if (a[i] == '/' &amp;&amp; a[i + 1] == '*') { sgn = 4; } if (a[i] == '#') { sgn = 3; } if (a[i] == 'i' &amp;&amp; a[i + 1] == 'n' &amp;&amp; a[i + 2] == 't') { if (check(i - 1) || check(i + 3)) { ; } else { printf(&quot;long long&quot;); i += 2; continue; } } } printf(&quot;%c&quot;, a[i]); } printf(&quot;\\n&quot;); if (sgn == 3 &amp;&amp; a[len - 1] != '\\\\')sgn = 0; memset(a, 0, sizeof(a)); } return 0; } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C7 - Solution-24航C","link":"https://github.pansis.site/post/C7 - Solution-24航C/","stats":{"text":"23 min read","time":1331000,"words":4569,"minutes":23},"dateFormat":"2024-11-18"},{"content":"C6 - Solution A 万圣夜糖果 难度 考点 1 排序 题意分析 不妨使用冒泡排序来完成此题。 在下面的示例代码中: 每完成一次 j 循环，都起到 “将第 iii 大的数组元素移到它应去的位置，即数组的倒数第 iii 个位置，即 a[n-i+1] 处”的效果。 当 i 从 111 遍历到 n−1n-1n−1 ，意味着从最大到倒数第二大的元素都去了正确的位置，这时倒数第一大的元素也只剩下正确的去处，因而完成了排序。 示例代码 #include&lt;stdio.h&gt; int n; int a[1010]; int main() { scanf(&quot;%d&quot;,&amp;n); for(int i=1 ; i&lt;=n ; i++) scanf(&quot;%d&quot;,&amp;a[i]); for(int i=1 ; i&lt;=n-1 ; i++) { for(int j=1 ; j&lt;=n-i ; j++) { if(a[j] &gt; a[j + 1]) { int temp = a[j]; a[j] = a[j + 1]; a[j + 1] = temp; } } } for(int i=1 ; i&lt;=n ; i++) printf(&quot;%d &quot;,a[i]); return 0; } B 单位向量 难度 考点 1 数组 题目分析 使用数组存储向量值，使用统计变量计算平方和，再按要求输出即可。 示例代码 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main(){ int n; scanf(&quot;%d&quot;, &amp;n); int a[105]; int sum = 0; // 平方和 for (int i = 0; i &lt; n; i++) { scanf(&quot;%d&quot;, &amp;a[i]); sum += a[i] * a[i]; // 累加 } for (int i = 0; i &lt; n; i++) { printf(&quot;%.3lf &quot;, a[i] / sqrt(sum)); // sqrt返回值为double，不需要再额外写类型转换操作 } return 0; } Author: SiSi C 阿瓦瑞吉 难度 考点 1 输入输出，数组 题意分析 本题中，我们面临不定数量的输入，且需要将它们存储进数组里。一种可行的处理方式是： 定义 int 类型的变量 n，希望其能代表“已读入的成绩的数量”的物理意义。显然初始时n=0;; 每读入一个成绩后，应令n=n+1 ; 不难注意到新读入的成绩应存放在 score[n] 这一位置里。 同时，只有完成读入之后才能得到成绩的总数 nnn ，进而求出平均值，而计算方差又需要平均值。因此，我们必须按照先完成读入、再求平均值、最后求方差的顺序编写程序。 示例代码 - 1 #include &lt;stdio.h&gt; int main() { int score[105]; int i=0, n; double average, variance; while(scanf(&quot;%d&quot;, &amp;score[i]) != EOF){ average += score[i]; i++; } n = i; average /= n; variance = 0.0; for(i=0; i&lt;n; i++){ variance += (score[i] - average) * (score[i] - average); } printf(&quot;%.2f %.2f\\n&quot;, average, variance/n); return 0; } 示例代码 - 2 也可以利用另一个方差公式 s2=x12+x22+x32+⋯+xn2n−(x1+x2+x3+⋯+xnn)2s^2=\\dfrac{x_1^{\\text{2}}+x_2^2+x_3^{\\text{2}}+\\cdots+x_n^2}{n}-\\left(\\dfrac{x_1+x_2+x_3+\\cdots+x_n}{n}\\right)^2s2=nx12​+x22​+x32​+⋯+xn2​​−(nx1​+x2​+x3​+⋯+xn​​)2 。 #include &lt;stdio.h&gt; #include&lt;math.h&gt; int main(){ int x[109]; int num=0,sum=0,sum2=0; while(scanf(&quot;%d&quot;,&amp;x[num])!=EOF){ sum+=x[num]; sum2+=(x[num]*x[num]); num++; } double ave=sum*1.0/num; double ave2=sum2*1.0/num; printf(&quot;%.2f &quot;,ave); printf(&quot;%.2f&quot;,ave2-ave*ave); return 0; } D 又一道A+B 难度 考点 2 循环，二维数组 题目分析 用二维数组储存矩阵，对应位置的数字相加即可。千万要注意开 long long 呀。 示例代码 #include &lt;stdio.h&gt; long long A[1000][1000],B[1000][1000]; int main(){ int n,m; scanf(&quot;%d%d&quot;,&amp;n,&amp;m); for(int i=0;i&lt;n;i++){ for(int j=0;j&lt;m;j++){ scanf(&quot;%lld&quot;,&amp;A[i][j]); } } for(int i=0;i&lt;n;i++){ for(int j=0;j&lt;m;j++){ scanf(&quot;%lld&quot;,&amp;B[i][j]); } } for(int i=0;i&lt;n;i++){ for(int j=0;j&lt;m;j++){ printf(&quot;%lld &quot;,A[i][j]+B[i][j]); } printf(&quot;\\n&quot;); } return 0; } E 雾里 难度 考点 3 二分法 题目分析 就是一道二分法的模板题，把课上的模板抄下来即可，注意是单调递减，要把条件的符号变一下，其余就乏善可陈了。 示例代码 #include &lt;math.h&gt; #include &lt;stdio.h&gt; #define eps 1e-9 double PI = 4.0 * atan(1.0); double f(double x) { return 666.0 * pow((1.0 / (2.0 * PI * x)), 1.5) * exp(-1.0 / x); } int main() { double y, l, r; while (scanf(&quot;%lf%lf%lf&quot;, &amp;l, &amp;r, &amp;y) != EOF) { double mid = l + (r - l) / 2.0; while (r - l &gt; eps) { mid = l + (r - l) / 2.0; f(mid) &lt; y ? (r = mid) : (l = mid); } printf(&quot;%.3f\\n&quot;, mid); } return 0; } F 小宇的名单 难度 考点 3 二分查找 题目分析 此题需要从升序数组中查找学号，并输出对应的姓名。需要注意的是查找的数组大小和查找次数的量级均为 10510^5105 ，这意味着查找的复杂度不能超过 O(n)O(n)O(n) ，因此不能采用线性查找方式，需要用到二分查找来解决本题。 示例代码 #include &lt;stdio.h&gt; int id[100009]; char name[100009][25]; int lower_bound(int a[], int lo, int hi, int val) { if (val &gt; a[hi]) return -1; int mi = 0; while (lo &lt; hi) { mi = (lo + hi) / 2; if (a[mi] &lt; val) lo = mi + 1; else hi = mi; } if (a[lo] == val) return lo; else return -1; } int main() { int n,m; scanf(&quot;%d%d&quot;,&amp;n,&amp;m); for (int i = 0; i &lt; n; ++i) { scanf(&quot;%d%s&quot;,&amp;id[i],name[i]); } for (int i = 0; i &lt; m; ++i) { int find_id; scanf(&quot;%d&quot;,&amp;find_id); int pos = lower_bound(id,0,n-1,find_id); if (pos == -1) printf(&quot;Not find!\\n&quot;); else printf(&quot;%s\\n&quot;,name[pos]); } } G s7h玩生命游戏 难度 考点 4 二维数组 题目分析 本题要求求一个生命游戏经过 TTT 个单位时间后的状态。显然，我们可以开两个二维数组，一个用来存储上一轮的状态，一个用来存储当前状态。在每一轮开始前，我们将当前状态拷贝到上一轮的状态即可。 示例代码 - 1 #include &lt;stdio.h&gt; #include&lt;math.h&gt; int last[105][105], now[105][105]; int alive(int i, int j) { int ans = last[i - 1][j - 1] + last[i][j - 1] + last[i + 1][j - 1] + last[i + 1][j] + last[i + 1][j + 1] + last[i][j + 1] + last[i - 1][j + 1] + last[i - 1][j]; return ans; } int main() { int n, m, t; scanf(&quot;%d%d%d&quot;,&amp;n,&amp;m,&amp;t); for (int i = 1; i &lt;= n; i++) { for (int j = 1; j &lt;= m; j++) { scanf(&quot;%d&quot;, &amp;now[i][j]); } } for (int k = 1; k &lt;= t; k++) { for (int i = 1; i &lt;= n; i++) { for (int j = 1; j &lt;= m; j++) { last[i][j] = now[i][j]; } } for (int i = 1; i &lt;= n; i++) { for (int j = 1; j &lt;= m; j++) { if (last[i][j] == 0) { if (alive(i, j) == 3) { now[i][j] = 1; } else { now[i][j] = 0; } } else { if (alive(i, j) &gt; 3 || alive(i, j) &lt; 2) { now[i][j] = 0; } else { now[i][j] = 1; } } } } } for (int i = 1; i &lt;= n; i++) { for (int j = 1; j &lt;= m; j++) { printf(&quot;%d &quot;, now[i][j]); } printf(&quot;\\n&quot;); } return 0; } 示例代码 - 2 本题要求求一个生命游戏经过 TTT 个单位时间后的状态。显然，我们可以开一个三维数组a[x][y][z]，其中前两个维度为空间维度，第三个维度为时间维度，表示第 z 秒时 (x,y) 这点的状态，然后根据规则编写代码即可。 一点点小优化： 注意到每次的状态仅与前一个状态有关，也就是说其实存下每一个时间的状态是多余的，我们只需存两个时间的状态就够了。 一个简单的实现方法是第三维度的大小只开 222 ，然后如果当前的 TTT 是奇数就存到 T=1T=1T=1 中，如果当前的的 TTT 是偶数就存到 T=0T=0T=0 中，访问当前状态只需使用 a[x][y][T%2]，访问上一时刻的状态只需用 a[x][y][(T-1)%2]即可。 #include&lt;stdio.h&gt; int m,n,T,a[102][102][2]; int main(){ scanf(&quot;%d%d%d&quot;,&amp;n,&amp;m,&amp;T); for(int i=1;i&lt;=n;++i) for(int j=1;j&lt;=m;++j) scanf(&quot;%d&quot;,&amp;a[i][j][0]); for(int t=1;t&lt;=T;++t){ for(int i=1;i&lt;=n;++i){ for(int j=1;j&lt;=m;++j){ int sum=a[i-1][j][(t-1)%2]+a[i+1][j][(t-1)%2]+a[i][j-1][(t-1)%2]+a[i][j+1][(t-1)%2]+a[i-1][j-1][(t-1)%2]+a[i+1][j-1][(t-1)%2]+a[i-1][j+1][(t-1)%2]+a[i+1][j+1][(t-1)%2];//sum记录一个格子周围八个格子中活细胞的数量。 if(a[i][j][(t-1)%2]==1&amp;&amp;(sum&gt;3||sum&lt;2)){ a[i][j][t%2]=0; } else if(a[i][j][(t-1)%2]==0&amp;&amp;sum==3){ a[i][j][t%2]=1; } else a[i][j][t%2]=a[i][j][(t-1)%2]; } } } for(int i=1;i&lt;=n;++i){ for(int j=1;j&lt;=m;++j){ printf(&quot;%d &quot;,a[i][j][T%2]); } putchar('\\n'); } return 0; } H 多项式加法 难度 考点 4 合并数组，数组越界 题目分析 对于本道题，如果多项式的每⼀项指数部分都在 [0,105][0,10^5][0,105] 这样的范围内，则我们只需要设置⼀个数组，每次读⼊的时候以指数部分作为数组下标，将系数记录进数组中即可完成任务。但本题中指数范围过大，直接采取上述方法进行计算将会导致数组越界，因此需要考虑其他方法。 注意到l两个函数都是以严格升序给出的，因此可以考虑这样⼀种做法： 用两个变量 i,ji,ji,j 记录当前正在处理 f(x)f(x)f(x) 的第 iii 项和 g(x)g(x)g(x) 的第 jjj 项，最初 i=j=1i=j=1i=j=1。 比较 f(x)f(x)f(x) 的第 iii 项和 g(x)g(x)g(x) 的第 jjj 项的指数： a. 若指数部分相同，说明这两项应该合并，系数应相减，输出，然后将 i,ji,ji,j 都向后移动⼀位； b. 若 f(x)f(x)f(x) 的第 iii 项的指数较小，说明只有 f(x)f(x)f(x) 的第 iii 项在当前计算的这一项中出现，输出，并将 iii 向后移动⼀位； c. 若 g(x)g(x)g(x) 的第 jjj 项的指数较小，说明只有 g(x)g(x)g(x) 的第 jjj 项在当前计算的这一项中出现，输出，并将 jjj 向后移动⼀位。 当 i,ji,ji,j 将 f(x),g(x)f(x),g(x)f(x),g(x) 的每一项都计算之后，最后结果的多项式也被计算出来了。可以发现，通过该方法得到的多项式，其指数部分也是严格递增的。 由于只会移动最多 m+nm+nm+n 次，因此总循环次数不超过 m+nm+nm+n 次，可以在题目要求的时间范围内完成计算。 示例代码 #include &lt;stdio.h&gt; #include&lt;math.h&gt; long long a[100006], b[100006], A[100006], B[100006]; long long c[200006], C[200006]; int main() { int m, n; int t; scanf(&quot;%d&quot;, &amp;t); while (t--) { scanf(&quot;%d&quot;, &amp;n); for (int i = 1; i &lt;= n; i++) { scanf(&quot;%lld&quot;, &amp;a[i]); } for (int i = 1; i &lt;= n; i++) { scanf(&quot;%lld&quot;, &amp;A[i]); } scanf(&quot;%d&quot;, &amp;m); for (int i = 1; i &lt;= m; i++) { scanf(&quot;%lld&quot;, &amp;b[i]); } for (int i = 1; i &lt;= m; i++) { scanf(&quot;%lld&quot;, &amp;B[i]); } int i = 1, j = 1, k = 1; while (i &lt;= m || j &lt;= n) { if (i == m + 1) { c[k] = b[j], C[k] = B[j]; j++; k++; } else if (j == n + 1) { c[k] = a[i], C[k] = A[i]; i++; k++; } else if (A[i] &lt; B[j]) { c[k] = a[i], C[k] = A[i]; i++; k++; } else if (B[j] &lt; A[i]) { c[k] = b[j], C[k] = B[j]; j++; k++; } else { if (a[i] + b[j] != 0) { c[k] = a[i] + b[j], C[k] = B[j]; } i++; j++; k++; } } printf(&quot;%d\\n&quot;, k - 1); for (int i = 1; i &lt;= k - 1; i++) { printf(&quot;%lld &quot;, c[i]); } printf(&quot;\\n&quot;); for (int i = 1; i &lt;= k - 1; i++) { printf(&quot;%lld &quot;, C[i]); } printf(&quot;\\n&quot;); } return 0; } I 优香的掷骰赛跑 2nd 难度 考点 5 递推、更高维的数组 题目分析 我们可以通过递推的方式解决本题。我们易知，n=0,a=0,b=0n=0,a=0,b=0n=0,a=0,b=0 时符合题意。因此我们可以从这个出发，我们知道了投掷 n−1n-1n−1 个骰子的情况时，我们可以递推，设优香多投掷了一个 1,3,41,3,41,3,4 点，或玛丽多投掷了一个 1,2,91,2,91,2,9 点，一共 666 种情况，逐个推导即可。我们可以设一个数组 s[n][a][b] 存储这个状态，逐个访问即可。 注意边界的处理方法，不要越界访问数组。 示例代码 #include &lt;stdio.h&gt; int s[50][350][350]; int main() { s[0][4][9] = 1; for (int i = 1; i &lt;= 40; i++) { for (int j = 4; j &lt;= 249; j++) { for (int k = 9; k &lt;= 249; k++) { s[i][j][k] = (s[i - 1][j - 1][k] | s[i - 1][j][k - 1] | s[i - 1][j - 3][k] | s[i - 1][j][k - 2] | s[i - 1][j - 4][k] | s[i - 1][j][k - 9] ); } } } int n,a,b; while(scanf(&quot;%d%d%d&quot;,&amp;n,&amp;a,&amp;b)!=EOF){ if(s[n][a+4][b+9] == 1){ printf(&quot;True\\n&quot;); }else{ printf(&quot;False\\n&quot;); } //printf(s[n][a+4][b+9]?&quot;True\\n&quot;:&quot;False\\n&quot;); } return 0; } J Cuvism3 难度 考点 6 三维前缀和，二分答案 题目分析 本题要求求一个三维空间图形中的最大正方体的边长。 我们先把问题简化一下：给定一个 ttt ，询问三维图形中是否有一个边长为 ttt 的正方体。 这个问题的一个简便解法是使用三维前缀和。前缀和是一种经过初始化后，可以以 O(1)O(1)O(1) 的时间复杂度查询区间和的算法，之前的练习赛我们已经向同学们介绍了一维前缀和的用法。而类比一维前缀和，我们可以拓展到二维前缀和，三维前缀和，甚至更高维的前缀和。 如果能以 O(1)O(1)O(1) 的时间复杂度查询sum(x0,y0,z0,x1,y1,z1)=∑i=x0x1∑j=y0y1∑k=z0z1a[i][j][k]sum(x_0,y_0,z_0,x_1,y_1,z_1)=\\sum_{i=x_0}^{x_1}\\sum_{j=y_0}^{y_1}\\sum_{k=z_0}^{z_1}a[i][j][k]sum(x0​,y0​,z0​,x1​,y1​,z1​)=∑i=x0​x1​​∑j=y0​y1​​∑k=z0​z1​​a[i][j][k]这么一个函数，对于每一个确定的 ttt，我们可以遍历 1≤x≤n−t+1,1≤y≤m−t+1,1≤z≤h−t+11\\le x\\le n-t+1,1\\le y\\le m-t+1,1\\le z\\le h-t+11≤x≤n−t+1,1≤y≤m−t+1,1≤z≤h−t+1 的所有点，假设该点是正方体顶点中 x,y,zx,y,zx,y,z 最小的点，验证 sum(x,y,z,x+t−1,y+t−1,z+t−1)sum(x,y,z,x+t-1,y+t-1,z+t-1)sum(x,y,z,x+t−1,y+t−1,z+t−1) 与 t3t^3t3 是否相等，如果相等则说明这个正方体空间是满的。对于一个给定的 ttt ，查询一次的时间复杂度为 O((n−t)×(m−t)×(h−t))O((n-t)\\times(m-t)\\times(h-t))O((n−t)×(m−t)×(h−t))。 类比一维前缀和要求出 sum[n]=∑i=1naisum[n]=\\sum_{i=1}^{n}a_isum[n]=∑i=1n​ai​ ，三维前缀和则需要求出 s[x][y][z]=∑i=1x∑j=1y∑k=1za[i][j][k]s[x][y][z]=\\sum_{i=1}^{x}\\sum_{j=1}^{y}\\sum_{k=1}^{z}a[i][j][k]s[x][y][z]=∑i=1x​∑j=1y​∑k=1z​a[i][j][k]。然而直接得到这个数组 并不简单，我们要将维度拆解，逐维求前缀和。 显然，我们可以用一个三重循环，类似于一维的方法，对于每一个点，固定第二维第三维，变化第一维，得到每一个点的线前缀和 sumline[x][y][z]=∑i=1xa[i][y][z]sumline[x][y][z] =\\sum_{i=1}^xa[i][y][z]sumline[x][y][z]=∑i=1x​a[i][y][z]。 之后拆解第二个维度，对于每一个点，固定其第一位第三维，变化第二维，叠加线前缀和，得到每一个点的面前缀和：sumplate[x][y][z]=∑i=1x∑j=1ya[i][j][z]=∑i=1ysumline[x][i]][z]sumplate[x][y][z]=\\sum_{i=1}^{x}\\sum_{j=1}^{y}a[i][j][z]=\\sum_{i=1}^{y}sumline[x][i]][z]sumplate[x][y][z]=∑i=1x​∑j=1y​a[i][j][z]=∑i=1y​sumline[x][i]][z]。 最后，对于每一个点，我们叠加其面前缀和即可得到体前缀和 s[x][y][z]=∑i=1x∑j=1y∑k=1za[i][j][k]=∑i=1zsumplate[x][y][i]s[x][y][z]=\\sum_{i=1}^{x}\\sum_{j=1}^{y}\\sum_{k=1}^{z}a[i][j][k]=\\sum_{i=1}^{z}sumplate[x][y][i]s[x][y][z]=∑i=1x​∑j=1y​∑k=1z​a[i][j][k]=∑i=1z​sumplate[x][y][i]。 接下来，我们要研究如何通过如何通过体 前缀和拆解得到 sum(x0,y0,z0,x1,y1,z1)=∑i=x0x1∑j=y0y1∑k=z0z1a[i][j][k]sum(x_0,y_0,z_0,x_1,y_1,z_1)=\\sum_{i=x_0}^{x_1}\\sum_{j=y_0}^{y_1}\\sum_{k=z_0}^{z_1}a[i][j][k]sum(x0​,y0​,z0​,x1​,y1​,z1​)=∑i=x0​x1​​∑j=y0​y1​​∑k=z0​z1​​a[i][j][k]。 经过一些简单的容斥和画图，我们可以得到答案： 显然，可以得到这样一个函数 int calc_sum(int x0,int y0,int z0,int x1,int y1,int z1){ return sum[x1][y1][z1]-sum[x0-1][y1][z1]-sum[x1][y0-1][z1]-sum[x1][y1][z0-1]+sum[x1][y0-1][z0-1]+sum[x0-1][y1][z0-1]+sum[x0-1][y0-1][z1]-sum[x0-1][y0-1][z0-1]; } （由于图中是连续的，实际是离散的，我们要把 x0,y0,z0x_0,y_0,z_0x0​,y0​,z0​ 各减一） 到此为止，在给定 ttt 的情况下，该问题已经得到解决。但是我们并没有这么一个 ttt。 显然，如果 t=αt=\\alphat=α 满足条件，则任意小于 α\\alphaα 的 ttt 也能满足条件。 我们或许可以从大到小遍历 ttt ，但是这样的话时间复杂度是 O(mnh×min⁡(m,n,h))O(mnh\\times \\min(m,n,h))O(mnh×min(m,n,h))，会 TLE 。 更明智的方案是通过二分答案算法去寻找边界的 ttt ，这样可以将时间复杂度降低至 O(mnh×log⁡min⁡(m,n,h))O(mnh\\times \\log \\min(m,n,h))O(mnh×logmin(m,n,h))。 关于二分答案的文章：二分 - OI Wiki 关于三维前缀和的更多内容：前缀和 &amp; 差分 - OI Wiki 示例代码 #include&lt;stdio.h&gt; int m,n,h,l,r,mid; int a[201][201][201],sum[201][201][201],sumline[201][201][201],sumplate[201][201][201]; int calc_sum(int x0,int y0,int z0,int x1,int y1,int z1){ return sum[x1][y1][z1]-sum[x0-1][y1][z1]-sum[x1][y0-1][z1]-sum[x1][y1][z0-1]+sum[x1][y0-1][z0-1]+sum[x0-1][y1][z0-1]+sum[x0-1][y0-1][z1]-sum[x0-1][y0-1][z0-1]; } int check(int x){//检验是否存在边长为 x 的正方体。 for(int i=1;i&lt;=n-x+1;++i) for(int j=1;j&lt;=m-x+1;++j) for(int k=1;k&lt;=h-x+1;++k) if(calc_sum(i,j,k,i+x-1,j+x-1,k+x-1)==x*x*x) return 1; return 0; } int main(){ scanf(&quot;%d%d%d&quot;,&amp;n,&amp;m,&amp;h); r=10000000; for(int k=1;k&lt;=h;++k) for(int i=1;i&lt;=n;++i) for(int j=1;j&lt;=m;++j) scanf(&quot;%d&quot;,&amp;a[i][j][k]); for(int i=1;i&lt;=n;++i) for(int j=1;j&lt;=m;++j) for(int k=1;k&lt;=h;++k) sumline[i][j][k]=sumline[i-1][j][k]+a[i][j][k]; for(int j=1;j&lt;=m;++j) for(int i=1;i&lt;=n;++i) for(int k=1;k&lt;=h;++k) sumplate[i][j][k]=sumplate[i][j-1][k]+sumline[i][j][k]; for(int k=1;k&lt;=h;++k) for(int j=1;j&lt;=m;++j) for(int i=1;i&lt;=n;++i) sum[i][j][k]=sum[i][j][k-1]+sumplate[i][j][k]; if(r&gt;n) r=n; if(r&gt;m) r=m; if(r&gt;h) r=h; l=1; mid=(l+r+1)/2; while(l&lt;r){ if(check(mid)) l=mid; else r=mid-1; mid=(l+r+1)/2; } printf(&quot;%d&quot;,mid); return 0; } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C6 - Solution-24航c","link":"https://github.pansis.site/post/C6 - Solution-24航c/","stats":{"text":"25 min read","time":1440000,"words":4855,"minutes":25},"dateFormat":"2024-11-03"},{"content":"E5 - Solution A 计算式2024F 难度 考点 1 函数 题意分析 按照题意，我们需要引入 math.h 库并调用其中的库函数 exp , cos , atan , log , cosh来进行计算。可以得到如下示例代码。注意输入为不定行输入，输出保留三位小数。 示例代码 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main() { double x, y; while (scanf(&quot;%lf&quot;, &amp;x) != EOF) { y = exp(cos(atan(x))) / log(cosh(x) + 1); printf(&quot;%.3f\\n&quot;, y); } return 0; } B 椭圆和离心率 难度 考点 1 函数 题目分析 直接根据 Hint 及有关的数学公式完成函数即可，注意函数的返回值为 double 类型，以及本题所需的数组大小。由 Hint 可知，本题需要讨论 a,ba,ba,b 的大小关系，这些都需要在函数中予以体现。 示例代码 #include &lt;stdio.h&gt; #include &lt;math.h&gt; double eccentricity(int a, int b) { double e = 0; if (a &gt; b) { e = sqrt(a * a - b * b) / a; } else if (a &lt; b) { e = sqrt(b * b - a * a) / b; } return e; } int main() { int p[9]; double e[6]; for (int i = 1; i &lt;= 8; i++) { scanf(&quot;%d&quot;, &amp;p[i]); } e[1] = eccentricity(p[1], p[5]); e[2] = eccentricity(p[3], p[1]); e[3] = eccentricity(p[7], p[6]); e[4] = eccentricity(p[2], p[4]); e[5] = eccentricity(p[8], p[3]); for (int i = 1; i &lt;= 5; i++) { printf(&quot;%.2f &quot;, e[i]); } return 0; } C 汉诺塔游戏 难度 考点 3 递归 题意分析 Hint中提到： 如果 nnn 为 111 ，你可以一步到位。否则，为了实现“将 111 到 nnn 号盘从 柱 xxx 移动到柱 yyy &quot; 的任务，你可以按如下三步走： 将 111 到 n−1n-1n−1 号盘从柱 xxx 移动到柱 zzz ,其中柱 zzz 是柱 x,yx,yx,y 之外的石柱; 将 nnn 号盘从柱 xxx 移动到柱 yyy ； 将 111 到 n−1n-1n−1 号盘从柱 zzz 移动到柱 yyy。 其中： 第 222 步显然是必需的； 为了达成第 222 步，需要让编号较小（即从 111 到 n−1n-1n−1）的盘从 nnn 号盘上移开，并在柱 zzz 上暂存，可见第 111 步也是必需的； 在达成第 222 步后，剩余的任务是将暂存在 zzz 柱上的第 111 到 n−1n-1n−1 号盘移动到 yyy 柱上，这正是第 333 步本身。 可见，上述对任务的拆分方法能够带来移动次数最少的方案。 示例代码 #include&lt;stdio.h&gt; int Step=1; //记录将要输出的步数。全局变量可以在任何函数内被使用 void Hanoi(int Num, int From, int To) //如此设计此函数，使调用其能够实现“将编号1到Num的盘从From柱移动到To柱”的效果 { if (Num==1) { printf(&quot;Step %d: Move plate %d from column %d to %d.\\n&quot;,Step,Num,From,To); Step++; } //若只有1号盘，直接移动即可 else { Hanoi(Num-1,From,6-From-To);// //显然，6-From-To可以得到From和To之外的柱的编号 printf(&quot;Step %d: Move plate %d from column %d to %d.\\n&quot;,Step,Num,From,To); Step++; Hanoi(Num-1,6-From-To,To); } } int main() { int n,x,y; scanf(&quot;%d%d%d&quot;,&amp;n,&amp;x,&amp;y); Hanoi(n,x,y); return(0); } D 三角形先生信息大公开 难度 考点 2 函数 题目分析 注意到该题数据范围，边长不大于 10510^5105 ，而余弦定理公式中存在对边长进行平方，结果最大为 101010^{10}1010 ，这超过了 int 范围（大约 2×1092\\times 10^92×109），解决方案有两个，使用 long long 或转换为 double。 示例代码-1 #include &lt;stdio.h&gt; #include &lt;math.h&gt; void solve(long long a, long long b, double C) { double c; c = 1.0*a*a + 1.0*b*b - 2.0*a*b*cos(C); // 转换为 double 类型 c = sqrt(c); printf(&quot;%.2f &quot;, c); if (a == b) { printf(&quot;isosceles triangle\\n&quot;); } else if (a % b == 0 || b % a == 0) { printf(&quot;exact division triangle\\n&quot;); } else if ((a &lt; b &amp;&amp; a * a &lt; b) || (a &gt; b &amp;&amp; b * b &lt; a)) { printf(&quot;great difference triangle\\n&quot;); } else { printf(&quot;Mr. triangle!\\n&quot;); } return; } int main() { int p1, p2, p3, p4, p5, p6; double a1, a2, a3; scanf(&quot;%d%d%d%d%d%d&quot;, &amp;p1, &amp;p2, &amp;p3, &amp;p4, &amp;p5, &amp;p6); scanf(&quot;%lf%lf%lf&quot;, &amp;a1, &amp;a2, &amp;a3); solve(p1, p5, a1); solve(p3, p1, a3); solve(p2, p6, a2); solve(p2, p4, a3); solve(p5, p3, a2); return 0; } 示例代码-2 #include &lt;stdio.h&gt; #include &lt;math.h&gt; void solve(long long a, long long b, double C) { double c; c = a*a + b*b - 2.0*a*b*cos(C); // 使用 long long 类型 c = sqrt(c); printf(&quot;%.2f &quot;, c); if (a == b) { printf(&quot;isosceles triangle\\n&quot;); } else if (a % b == 0 || b % a == 0) { printf(&quot;exact division triangle\\n&quot;); } else if ((a &lt; b &amp;&amp; a * a &lt; b) || (a &gt; b &amp;&amp; b * b &lt; a)) { printf(&quot;great difference triangle\\n&quot;); } else { printf(&quot;Mr. triangle!\\n&quot;); } return; } int main() { long long p1, p2, p3, p4, p5, p6; double a1, a2, a3; scanf(&quot;%ld%ld%ld%ld%ld%ld&quot;, &amp;p1, &amp;p2, &amp;p3, &amp;p4, &amp;p5, &amp;p6); scanf(&quot;%lf%lf%lf&quot;, &amp;a1, &amp;a2, &amp;a3); solve(p1, p5, a1); solve(p3, p1, a3); solve(p2, p6, a2); solve(p2, p4, a3); solve(p5, p3, a2); return 0; } 大家以后再遇到这种样例过了但 OJ 不过的问题多考虑一下数据范围啊！动动小手修改数据类型试试，花不了多少时间的（笑）。还要注意，不是保证用于存结果的变量数据类型够大就可以的，赋值等号右边的式子在计算过程中超范围就不行了。详见 C2-A, C2-B, E2-C, E2-F 。 E -M3rcury-函数 难度 考点 3 递归 题目分析 按照题目中的递归式计算并统计答案即可 示例代码 #include &lt;stdio.h&gt; int m(int n) { if(n==1) return 1; else if(n==3) return 3; else if(n%2==0) return m(n/2); else if(n%4==1) return 2*m((n-1)/2+1)-m((n-1)/4); else return 3*m((n-3)/2+1)-2*m((n-3)/4); } int main() { int n; scanf(&quot;%d&quot;,&amp;n); int ans=0; for(int i=1;i&lt;=n;i++) { if(m(i)==i) ans++; } printf(&quot;%d&quot;,ans); return 0; } F 孪生素数猜想 2024 难度 考点 4 函数，判断素数 题目分析 本题与教材例5-4类似。 我们只需要使 ppp 从 lll 开始遍历到 r−2r-2r−2 ，判断 ppp 和 q=p+2q=p+2q=p+2 是否为素数，输出结果即可。 判断素数的函数是下方示例代码中的 isPrime 函数，若参数 xxx 是素数则返回 111 ，否则返回 000 。 注意：判断素数时 iii 只需要枚举到 x\\sqrt xx​ 即可，若枚举到 xxx 会超时；此外特别要注意 111 不是素数。 示例代码中主函数中的循环是从大于 lll 的第一个奇数 (p = l | 1 )开始枚举的，从 lll 开始枚举也没有问题。 示例代码 #include &lt;stdio.h&gt; int isPrime(int x) { if(x == 1) return 0; if(x % 2 == 0) return 0; for(int i = 3; i * i &lt;= x; i += 2) { if(x % i == 0) return 0; } return 1; } int main() { int l, r; int n = 0; scanf(&quot;%d%d&quot;, &amp;l, &amp;r); //for(int p = l; p + 2 &lt;= r; p++) for(int p = l | 1; p + 2 &lt;= r; p += 2) { if(isPrime(p) &amp;&amp; isPrime(p + 2)) { printf(&quot;%d %d\\n&quot;, p, p + 2); n++; } } printf(&quot;%d&quot;, n); return 0; } G Firefly小姐的递归函数 难度 考点 3 递归 题目分析 自定义函数 long long f(long long n, long long k, long long p)，根据题意写出递归逻辑即可。 只要你不在哪个犄角旮旯里漏了点东西（比如 %p 或者算 n2n^2n2 用了有精度问题的 pow 函数），本题应该很容易实现。 示例代码 #include &lt;stdio.h&gt; long long f(long long n, long long k, long long p){ if (k == 0) { return 1; } else if (k &amp; 1){ return n * f(n * n % p, k &gt;&gt; 1, p) % p; // 分情况递归调用f } else { return f(n * n % p, k &gt;&gt; 1, p) % p; } } int main(){ long long n, k, p; scanf(&quot;%lld %lld %lld&quot;, &amp;n, &amp;k, &amp;p); printf(&quot;%lld&quot;, f(n, k, p)); } Author:SiSi{\\LARGE {\\color{yellowgreen} \\mathscr{Author:SiSi}}}Author:SiSi H Cantor Set 难度 考点 5 递归输出 题目分析 分形图案的特征就是具备“自相似性”，而康托集正是最基础的分形图案。 而递归就是把大问题分解为相同类型的小问题，我们可以使用递归函数输出一个分形图案。 思路一 设函数 f(n,k)f(n, k)f(n,k) 的功能是“输出 nnn 次迭代中第 kkk 次迭代的一整行”，则 f(n,k)f(n,k)f(n,k) 的递归方案为： 当 k=0k = 0k=0 时，输出 3n3^n3n 个字符 -； 当 k&gt;0k &gt; 0k&gt;0 时，调用 f(n−1,k−1)f(n-1, k - 1)f(n−1,k−1)，然后输出 3n−13^{n-1}3n−1 个空格，然后再调用 f(n−1,k−1)f(n-1, k - 1)f(n−1,k−1)； 示例代码 1 #include &lt;stdio.h&gt; int power_3[10] = {1}; void f(int n, int k) // 输出n次迭代中第i次迭代的一整行（不输出换行） { if(k == 0) { for(int i = 0; i &lt; power_3[n]; ++i) printf(&quot;%c&quot;, '-'); } else { f(n - 1, k - 1); for(int i = 0; i &lt; power_3[n - 1]; ++i) printf(&quot;%c&quot;, ' '); f(n - 1, k - 1); } } int main() { int n; scanf(&quot;%d&quot;, &amp;n); for(int i = 1; i &lt;= n; ++i) // 求出3的幂（每部分的字符个数） power_3[i] = 3 * power_3[i - 1]; for(int i = 0; i &lt;= n; ++i) { f(n, i); printf(&quot;\\n&quot;); // 每输出一行后要换行 } return 0; } 思路二 设函数 f(n,i,j)f(n, i, j)f(n,i,j) 的功能是“返回 nnn 次迭代中第 iii 次迭代的一行中的第 jjj 个字符”，则 f(n,i,j)f(n, i, j)f(n,i,j) 的递归方案为： 若 i=0i=0i=0，一整行都是字符 -，因此返回字符 -； 若 i&gt;0i&gt;0i&gt;0，当该字符在一行的中间去掉的部分时，即 3n−1&lt;j≤2×3n−13^{n-1} &lt; j \\le 2\\times 3^{n-1}3n−1&lt;j≤2×3n−1，返回空格符； 若 i&gt;0i&gt;0i&gt;0，当该字符在一行的左侧部分时，即 1≤j≤3n−11\\le j\\le 3^{n-1}1≤j≤3n−1，返回 f(n−1,i−1,j)f(n-1,i-1,j)f(n−1,i−1,j)； 若 i&gt;0i&gt;0i&gt;0，当该字符在一行的右侧部分时，即 2×3n−1&lt;j≤3n2\\times 3^{n-1}&lt;j\\le3^n2×3n−1&lt;j≤3n，返回 f(n−1,i−1,j−2×3n−1)f(n-1,i-1,j - 2\\times 3^{n-1})f(n−1,i−1,j−2×3n−1)。 如果下标 jjj 从第 000 个开始记，则第二种情况的条件可以写作 ⌊j3n−1⌋=1\\left\\lfloor\\frac j{3^{n-1}}\\right\\rfloor=1⌊3n−1j​⌋=1；后两种情况可以统一起来，返回值可以写作 f(n−1,i−1,j mod 3n−1)f(n-1,i-1,j\\bmod 3^{n-1})f(n−1,i−1,jmod3n−1)。 示例代码 2 #include &lt;stdio.h&gt; int power_3[10] = {1}; char f(int n, int i, int j) // 返回n次迭代中第i次迭代的一行中的第j个字符 { if(i == 0) return '-'; else if(j / power_3[n - 1] == 1) return ' '; else return f(n - 1, i - 1, j % power_3[n - 1]); } int main() { int n; scanf(&quot;%d&quot;, &amp;n); for(int i = 1; i &lt;= n; ++i) // 求出3的幂（每部分的字符个数） power_3[i] = 3 * power_3[i - 1]; for(int i = 0; i &lt;= n; ++i) { for(int j = 0; j &lt; power_3[n]; ++j) printf(&quot;%c&quot;, f(n, i, j)); printf(&quot;\\n&quot;); // 每输出一行后要换行 } return 0; } 思路三 与思路一类似。 设函数 f(n,k)f(n, k)f(n,k) 的功能为： 当 k≥0k\\ge 0k≥0 时，与思路一中的 f(n,k)f(n,k)f(n,k) 功能相同，即“输出 nnn 次迭代中第 kkk 次迭代的一整行”； 当 k=−1k = -1k=−1 时，表示输出 3n3^n3n 个空格。 则 f(n,k)f(n,k)f(n,k) 的递归方案为： 当 n=0n = 0n=0 时，根据 kkk 是否为 −1-1−1 输出一个空格或一个字符 -； 当 n&gt;0n&gt;0n&gt;0 时，若 k&gt;0k &gt; 0k&gt;0，调用 f(n−1,k−1)f(n-1, k - 1)f(n−1,k−1)，然后调用 f(n−1,−1)f(n - 1, -1)f(n−1,−1) 输出 3n−13^{n-1}3n−1 个空格，然后再调用 f(n−1,k−1)f(n-1, k - 1)f(n−1,k−1)​； 当 n&gt;0n&gt;0n&gt;0 时，若 k=0k = 0k=0 或 k=−1k = -1k=−1 ，则调用三次 f(n−1,k)f(n - 1, k)f(n−1,k)​​。 示例代码 3 #include &lt;stdio.h&gt; void f(int n, int k) { if(n == 0) putchar(k == -1 ? '-' : ' '); else if(k &gt; 0) { f(n - 1, k - 1); f(n - 1, -1); f(n - 1, k - 1); } else { f(n - 1, k); f(n - 1, k); f(n - 1, k); } } int main() { int n; scanf(&quot;%d&quot;, &amp;n); for(int i = 0; i &lt;= n; ++i) { f(n, i); printf(&quot;\\n&quot;); // 每输出一行后要换行 } return 0; } I 间隔取数 难度 考点 5 动态规划 题目分析 两个数组 {dp1i}\\{dp1_i\\}{dp1i​} 和 {dp2i}\\{dp2_i\\}{dp2i​} 分别表示只考虑前 iii 个数时，取第 iii 个数和不取第 iii 个数的总和最大值。 有状态转移方程 dp1i=dp2i−k+aidp1_i=dp2_{i-k}+a_idp1i​=dp2i−k​+ai​ ，dp2i=max⁡{dp1i−1, dp2i−1}dp2_i=\\max\\{dp1_{i-1},\\ dp2_{i-1}\\}dp2i​=max{dp1i−1​, dp2i−1​} 。 我们以样例为例，以表格的方式体会一下两个数组的运作方式 iii 0 1 2 3 4 5 前 iii 个数 1 1,1 1,1,4 1,1,4,5 1,1,4,5,1 1,1,4,5,1,4 dp1idp1_idp1i​ 1 1 5 6 6 10 所选数 1 1 1,4 1,5 1,4,1 1,5,4 递推来源 \\ dp20dp2_0dp20​ dp21dp2_1dp21​ dp22dp2_2dp22​ dp23dp2_3dp23​ dp24dp2_4dp24​ dp2idp2_idp2i​ 0 1 1 5 6 6 所选数 \\ 1 1 1,4 1,5 1,4,1或1,5 递推来源 \\ dp10dp1_0dp10​ dp11dp1_1dp11​ 或 dp21dp2_1dp21​ dp12dp1_2dp12​ dp13dp1_3dp13​ dp14dp1_4dp14​ 或 dp24dp2_4dp24​ （上表大家也可以尝试一下自己推导，可以帮助理解 示例代码 #include &lt;stdio.h&gt; #define max(a,b) a&gt;b?a:b int dp1[100005];//选当前数的最大值 int dp2[100005];//不选当前数的最大值 int a[100005]; int n, k; int main(){ scanf(&quot;%d %d&quot;, &amp;n, &amp;k); for (int i=0; i&lt;n; i++){ scanf(&quot;%d&quot;, &amp;a[i]); if (i==0) {//初状态 dp1[i]=a[0]; dp2[i]=0; }else if(i&lt;k){//初状态 dp1[i]=a[i]; dp2[i]=max(dp1[i-1], dp2[i-1]); }else { dp1[i]=dp2[i-k]+a[i]; dp2[i]=max(dp1[i-1], dp2[i-1]); } } printf(&quot;%d&quot;, max(dp1[n-1], dp2[n-1])); return 0; } J Fractal 难度 考点 5 递归、循环、二维数组 题意分析 思路一：递归输出每一行 定义一个函数 f(k,l,m)f(k,l,m)f(k,l,m) ，表示输出 kkk 阶图案第 mmm 个部分的第 lll 行， m=0,1,2,3m = 0, 1, 2, 3m=0,1,2,3 分别代表左上、右上、左下、右下部分。 以左上部分为例，可以发现，对于 kkk 阶图案的左上部分，是由 k−1k-1k−1 阶图案的左上、右上、左下部分和空白组成的。如果 kkk 阶图案左上部分第 lll 行在左上部分的上半部分，则其等于 k−1k-1k−1 阶图案的左上和右上部分；如果第 lll 行在左上部分的下半部分，则等于 k−1k-1k−1 阶图案的左下部分和相同大小的空白。 其他情况与左上部分类似，分类讨论递归即可完成本题。 递归基本情况是 k=0k = 0k=0 时，左上、右上、左下、右下部分均为一个 1 ； kkk 阶图案的边长为 2k+12^{k+1}2k+1 ，其左上、右上、左下、右下部分的边长为 2k2^k2k ，每个部分均由四个边长为 2k−12^{k-1}2k−1 的小部分组成。 具体实现参照示例代码及注释。 思路二：递归输出每个位置 定义一个函数 f(k,i,j)f(k, i, j)f(k,i,j) ，表示输出 kkk 阶图案第 iii 行第 jjj 个位置（从第0行第0列开始记）。 若 k=0k=0k=0 ，则输出 1 ； 否则，若位置 (i,j)(i, j)(i,j) 在中央，即 i,j∈[2k−1,3⋅2k−1)i,j\\in[2^{k-1},3\\cdot2^{k-1})i,j∈[2k−1,3⋅2k−1) ，则输出空格； 否则，输出 f(k−1,i′,j′)f(k-1, i&#x27;, j&#x27;)f(k−1,i′,j′) ，其中 i′=i mod 2k, j′=j mod 2ki&#x27;=i\\bmod 2^k,\\ j&#x27;=j\\bmod 2^ki′=imod2k, j′=jmod2k 。 具体实现参照示例代码及注释。 该思路耗时比思路一更长，但较为简单。 思路三：循环+二维数组（会MLE） 由于二维数组还未学到，本思路仅作为参考。 由 kkk 阶图案生成 k+1k+1k+1 阶图案可由下列两步操作实现： 将 kkk 阶图案复制 444 份，拼在一起形成一个大正方形（即向右、向下、向右下复制平移）； 将该大正方形中心与 kkk 阶图案相同大小的区域置为空白。 依次思路， kkk 阶图案仅需要从原始图案重复执行上列操作 kkk 遍即可得到。 具体实现参照示例代码及注释。 补充 学过二维数组后似乎思路三要更加的简单一些，但是这种规律并不具有普遍性，换一个分形图案就无法用相同的规律生成了，而递归的思路却是类似的。此外思路三的空间消耗非常大，虽然本题不会爆内存，但是推荐大家写程序时考虑占用的空间大小。 示例代码 思路一：递归输出每一行 #include &lt;stdio.h&gt; void f(int k, int l, int m) { if(!k) printf(&quot;1&quot;); //基本情况，k=0，此时l一定为1，无论m是0,1,2,3，均输出一个1 else if(l &lt; (1 &lt;&lt; (k - 1))) //如果l小于2^(k-1)，即该行在左上或右上或左下或右下部分的上半部分 { switch(m) //根据m的值进行分类讨论 { case 2: //左下部分的上半部分 f(k - 1, l, 0); //输出k-1阶的左上部分的第l行 for(int i = 0; i &lt; (1 &lt;&lt; (k - 1)); ++i) printf(&quot; &quot;); //输出相同长度的空格 break; case 3: //右下部分的上半部分 for(int i = 0; i &lt; (1 &lt;&lt; (k - 1)); ++i) printf(&quot; &quot;); //输出相同长度的空格 f(k - 1, l, 1); //输出k-1阶的右上部分的第l行 break; default: //左上或右上部分的上半部分 f(k - 1, l, 0); //输出k-1阶的左上部分的第l行 f(k - 1, l, 1); //输出k-1阶的右上部分的第l行 break; } } else //l不小于2^(k-1)，说明该行在左上或右上或左下或右下部分的下半部分 { l -= (1 &lt;&lt; (k - 1)); //更新l的值为k-1阶左上或右上或左下或右下部分对应的行数，即自减2^(k-1) switch(m) //根据m的值进行分类讨论 { case 0: //左上部分的下半部分 f(k - 1, l, 2); //输出k-1阶的左下部分的第l行 for(int i = 0; i &lt; (1 &lt;&lt; (k - 1)); ++i) printf(&quot; &quot;); //输出相同长度的空格 break; case 1: //右上部分的下半部分 for(int i = 0; i &lt; (1 &lt;&lt; (k - 1)); ++i) printf(&quot; &quot;); //输出相同长度的空格 f(k - 1, l, 3); //输出k-1阶的右下部分的第l行 break; default: //左下或右下部分的下半部分 f(k - 1, l, 2); //输出k-1阶的左下部分的第l行 f(k - 1, l, 3); //输出k-1阶的右下部分的第l行 break; } } } int main() { int k; scanf(&quot;%d&quot;, &amp;k); for(int i = 0; i &lt; (1 &lt;&lt; k); ++i) { f(k, i, 0); //输出左上部分的第i行 f(k, i, 1); //输出右上部分的第i行 printf(&quot;\\n&quot;); //每输出一整行要换行 } for(int i = 0; i &lt; (1 &lt;&lt; k); ++i) { f(k, i, 2); //输出左下部分的第i行 f(k, i, 3); //输出右下部分的第i行 printf(&quot;\\n&quot;); //每输出一整行要换行 } return 0; } 思路二：递归输出每个位置 #include &lt;stdio.h&gt; void f(int i, int j, int k) //输出k阶分形的第i行第j列 { if (k == 0) printf(&quot;1&quot;); else if (1 &lt;&lt; k - 1 &lt;= i &amp;&amp; i &lt; 3 &lt;&lt; k - 1 &amp;&amp; 1 &lt;&lt; k - 1 &lt;= j &amp;&amp; j &lt; 3 &lt;&lt; k - 1) printf(&quot; &quot;); else f(i % (1 &lt;&lt; k), j % (1 &lt;&lt; k), k - 1); } int main() { int k; scanf(&quot;%d&quot;, &amp;k); for (int i = 0; i &lt; (1 &lt;&lt; (k + 1)); i++) { for (int j = 0; j &lt; (1 &lt;&lt; (k + 1)); j++) f(i, j, k); puts(&quot;&quot;); } return 0; } 思路三：循环+二维数组 #include &lt;stdio.h&gt; int a[2048][2048] = {{1, 1}, {1, 1}}; //初始为四个1 int main() { int K; scanf(&quot;%d&quot;, &amp;K); for(int k = 1; k &lt;= K; ++k) //K遍操作 { for(int i = 0; i &lt; (1 &lt;&lt; k); ++i) for(int j = 0; j &lt; (1 &lt;&lt; k); ++j) a[i + (1 &lt;&lt; k)][j + (1 &lt;&lt; k)] = a[i + (1 &lt;&lt; k)][j] = a[i][j + (1 &lt;&lt; k)] = a[i][j]; //向右、下、右下复制平移 for(int i = 0; i &lt; (1 &lt;&lt; k); ++i) for(int j = 0; j &lt; (1 &lt;&lt; k); ++j) a[i + (1 &lt;&lt; (k - 1))][j + (1 &lt;&lt; (k - 1))] = 0; //中心部分置零 } for(int i = 0; i &lt; (1 &lt;&lt; (K + 1)); ++i) { for(int j = 0; j &lt;= (1 &lt;&lt; (K + 1)); ++j) printf(&quot;%c&quot;, a[i][j] ? '1' : ' '); //输出，若为1则输出1，若为0则输出空格 printf(&quot;\\n&quot;); //每行之间要换行 } return 0; } Author: 哪吒 K 64位全加器先生 难度 考点 3 递归 题目分析 我们要算出 a+ba+ba+b ，如果其中一个是 000 ，那么我们无需做加法，直接返回确定的值即可。如果都不是 000 ，则根据 (a + b) == (a ^ b) + ((a &amp; b) &lt;&lt; 1) 规则递归即可。 示例代码 #include&lt;stdio.h&gt; unsigned long long plus(unsigned long long a,unsigned long long b){ if(a==0)return b; if(b==0)return a; return plus(a^b,(a&amp;b)&lt;&lt;1); } int main(){ unsigned long long a,b; scanf(&quot;%llu%llu&quot;,&amp;a,&amp;b); printf(&quot;%llu&quot;,plus(a,b)); return 0; } L 猜猜自己的幸运数字 难度 考点 1 二分 题目分析 先设上下限 a=0,b=60a=0,b=60a=0,b=60 。 若 a+b2\\dfrac{a+b}{2}2a+b​ 小于幸运数字，则 bbb 不变， a=a+b2a=\\dfrac{a+b}{2}a=2a+b​ ，重复该过程； 若 a+b2\\dfrac{a+b}{2}2a+b​ 大于幸运数字，则 aaa 不变， b=a+b2b=\\dfrac{a+b}{2}b=2a+b​ ，重复该过程。 直至猜中为止。 ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"E5 - Solution-24航c","link":"https://github.pansis.site/post/E5 - Solution-24航c/","stats":{"text":"27 min read","time":1589000,"words":5511,"minutes":27},"dateFormat":"2024-11-03"},{"content":"二分法解方程 //所解方程在选定区间内必须单调 #include &lt;stdio.h&gt; double f(double x) { return x * x;//函数表达式,此处f(x)=x*x } double solve(double low, double high, double eps, double fx) //eps为误差范围 fx为所求函数值 //适用于单增函数 { double mid; while (high - low &gt; eps) { mid = (high + low) / 2; if (f(mid) &gt; fx) //若为单减函数，仅需改变不等号（此处适用于单增函数） { high = mid; } else low = mid; } return low; } int main() { printf(&quot;%f&quot;, solve(0, 2, 0.00001, 2));//计算f(x)=2的解(x在0~2范围内) } 冒泡排序 /*冒泡排序法 */ #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; void maopao(int a[],int n) //升序 {int temp,aa=0; for (int i = 0; i &lt; n-1; ++i) { aa=0; for (int j = 0; j &lt; n-1-i; ++j) { if (a[j]&gt;a[j+1]) {temp=a[j+1]; aa=1; a[j+1]=a[j]; a[j]=temp; } } if (aa==0) break; } } void maopao1(int a[],int n) //降序 {int temp,aa=0; for (int i = 0; i &lt; n-1; ++i) { aa=0; for (int j = 0; j &lt; n-1-i; ++j) { if (a[j]&lt;a[j+1]) {temp=a[j+1]; aa=1; a[j+1]=a[j]; a[j]=temp; } } if (aa==0) break; } } int main() { int n;int a[1002]={0};int i; scanf(&quot;%d&quot;,&amp;n); for (i = 0; i &lt;n ; ++i) { scanf(&quot;%d&quot;,&amp;a[i]); } maopao(a,i); for (i = 0; i &lt;n ; ++i) { printf(&quot;%d&quot;,a[i]); } } 二分查找 1、升序数组中找第一个大于等于某个数的值 1.条件：数组升序排列 2.返回值：数组中从左到右第一个大于等于某值的下标 3.代码： //下界二分查找 //a: 要查找的有序数组, 默认是从小到大排序 //lo, hi: 要查找的范围是从a[lo]到a[hi]之间(包含a[lo]和a[hi]) //val: 要查找的值 //返回的值: 找到的元素在数组a中的下标 //如果所有数都小于val,则返回hi+1 int lower_bound(int a[], int lo, int hi, int val) { if (val &gt; a[hi]) return hi + 1; int mi = 0; while (lo &lt; hi) { mi = (lo + hi) &gt;&gt; 1; if (a[mi] &lt; val) lo = mi + 1; else hi = mi; } return lo;} 4.具体例子如下: 假如说我们要查找的有序数列为{1,2,2,2,3,5} 此时假定数组第一个元素的下标为0 在查找2的时候，那么我们此时返回的下标就是1 (注意：只需要找到第一个) 在查找3的时候，那么我们此时返回的下标就是4 在查找4的时候，那么我们此时返回的下标就是5 在查找6的时候，那么我们此时返回的下标就是6 2、升序数组中找第一个大于某个数的值 1.条件：数组升序排列 2.返回值：数组中从左到右第一个严格大于某值的数值的数组下标 3.代码： //上界二分查找 //a: 要查找的有序数组, 默认是从小到大排序 //lo, hi: 要查找的范围是从a[lo]到a[hi]之间(包含a[lo]和a[hi]) //val: 要查找的值 //返回的值: 找到的元素在数组a中的下标 //如果所有数都小于等于val,则返回hi+1 int upper_bound(int a[], int lo, int hi, int val) { if (val &gt;= a[hi]) return hi + 1; int mi = 0; while (lo &lt; hi) { mi = (lo + hi) &gt;&gt; 1; if (a[mi] &lt;= val) lo = mi + 1; else hi = mi;} return lo;} 4.具体例子如下: 假如说我们要查找的有序数列为{1,2,2,2,3,5} 此时假定数组第一个元素的下标为0 在查找1 的时候，那么我们此时返回的下标就是1 (注意：这里要找的是第一个严格大于的) 在查找3 的时候，那么我们此时返回的下标就是5 在查找5 的时候，那么我们此时返回的下标就是6 (这个下标不存在，他反应的就是数列里的所有数都不大于5 ，返回的是下标，所以也不需要担心数 组越界) 3、 降序数组中找第一个小于某个数的值 upper_bound 在一个有序的序列中，找到第一个严格小于某个数的值 1.条件：数组降序排列 2.返回值：数组中从左到右第一个严格小于某值的数值的数组下标 3.代码： //a: 要查找的有序数组, 默认是从大到小排序 //lo, hi: 要查找的范围是从a[lo]到a[hi]之间(包含a[lo]和a[hi]) //val: 要查找的值 //返回的值: 找到的元素在数组a中的下标 //如果所有数都大于等于val,则返回hi+1 int upper_bound(int a[], int lo, int hi, int val) { if (val &lt;= a[hi]) return hi + 1; int mi = 0; while (lo &lt; hi) { mi = (lo + hi) &gt;&gt; 1; if (a[mi] &gt;= val) lo = mi + 1; else hi = mi;} return lo;} 4、降序数组中找第一个小于等于某个数的值 upper_bound 在一个有序的序列中，找到第一个小于等于某个数的值 1.条件：数组降序排列 2.返回值：数组中从左到右第一个小于等于某值的数值的数组下标 3.代码： //a: 要查找的有序数组, 默认是从大到小排序 //lo, hi: 要查找的范围是从a[lo]到a[hi]之间(包含a[lo]和a[hi]) //val: 要查找的值 //返回的值: 找到的元素在数组a中的下标 //如果所有数都大于val,则返回hi+1 int upper_bound(int a[], int lo, int hi, int val) { if (val &lt; a[hi]) return hi + 1; int mi = 0; while (lo &lt; hi) { mi = (lo + hi) &gt;&gt; 1; if (a[mi] &gt; val) lo = mi + 1; else hi = mi;} return lo;} 5、Tips 1、需要注意的是，上述几个二分查找并不会去判定该值是否存在于数组当中，因此上述函数返回的下标对应的值可能并不是我们要查找的值。要判断该值是否存在于数组中，则需要用if去判断一下返回的下标对应的值是不是我们要查找的值，如果不相等，则说明该值不存在于数组中。 （之所以不设置不存在于数组中函数返回-1，是为了函数的通用性着想） ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"常用函数2-24航c","link":"https://github.pansis.site/post/常用函数2-24航c/","stats":{"text":"7 min read","time":405000,"words":1562,"minutes":7},"dateFormat":"2024-10-30"},{"content":"质数判断 判断num是不是质数，如果是质数返回1，如果不是质数返回0 int iszhi(int num) { if (num==1) { return 0; } if (num==2) { return 1; } for (int i = 2; i &lt;= sqrt(num); ++i) { if (num%i==0) { return 0; } } return 1; } 闰年判断 判断year是不是闰年，如果是闰年返回1，不是闰年返回0 int isrun(int year) { return (year%4==0&amp;&amp;year%100!=0)||year%400==0; } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"常见函数-24航c","link":"https://github.pansis.site/post/常见函数-24航c/","stats":{"text":"1 min read","time":29000,"words":103,"minutes":1},"dateFormat":"2024-10-24"},{"content":"C A129 普通递归 #include&lt;stdio.h&gt; #include&lt;math.h&gt; long long f(int n) { if (n==1) return 1; if (n==2) return 2; return 2*f(n-1)+f(n-2); } int main() { int n; scanf(&quot;%d&quot;,&amp;n); printf(&quot;%lld\\n&quot;,f(n)); } 记忆化递归 #include&lt;stdio.h&gt; #include&lt;math.h&gt; long long ff[40]; long long f(int n) { if (n==1) return 1; if (n==2) return 2; if (ff[n]!=0) return ff[n]; ff[n]=2*f(n-1)+f(n-2); return ff[n]; } int main() { int n; scanf(&quot;%d&quot;,&amp;n); printf(&quot;%lld\\n&quot;,f(n)); } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C5讲解(记忆化递归)-24航c","link":"https://github.pansis.site/post/C5讲解(记忆化递归)-24航c/","stats":{"text":"1 min read","time":45000,"words":126,"minutes":1},"dateFormat":"2024-10-24"},{"content":"C5 - Solution A 二分类交叉熵 难度 考点 1 math.h 题目分析 照着公式写就行，别忘了 %.3lf 或者 %.3f 输出就行 ~&gt;_&lt;~ 示例代码 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main() { int y; double y_hat; scanf(&quot;%d %lf&quot;, &amp;y, &amp;y_hat); printf(&quot;%.3lf&quot;, -y * log2(y_hat) - (1 - y) * log2(1 - y_hat)); return 0; } Author:SiSi{\\LARGE {\\color{yellowgreen} \\mathscr{Author:SiSi}}}Author:SiSi B 三角形女士信息大公开 难度 考点 1 函数 题目分析 观察表格，发现每个三角形三个边的组成没有任何规律，无法使用循环解决，便考虑将重复计算过程封装成函数，建立三边长与面积的一一对应关系。 示例代码 - 1 #include &lt;stdio.h&gt; #include &lt;math.h&gt; void solve(int a, int b, int c) { double p = 1.0 * (a + b + c) / 2; double s = p * (p - a) * (p - b) * (p - c); printf(&quot;%.3f\\n&quot;, sqrt(s)); return; } int main() { int p1, p2, p3, p4, p5, p6; scanf(&quot;%d%d%d%d%d%d&quot;, &amp;p1, &amp;p2, &amp;p3, &amp;p4, &amp;p5, &amp;p6); solve(p1, p5, p1); solve(p3, p1, p3); solve(p2, p6, p2); solve(p2, p4, p3); solve(p5, p3, p2); return 0; } 示例代码 - 2 采用 double 类型的函数。 #include &lt;stdio.h&gt; #include &lt;math.h&gt; double solve(int a, int b, int c) { double p = 1.0 * (a + b + c) / 2; double s = p * (p - a) * (p - b) * (p - c); return sqrt(s); } int main() { int p1, p2, p3, p4, p5, p6; scanf(&quot;%d%d%d%d%d%d&quot;, &amp;p1, &amp;p2, &amp;p3, &amp;p4, &amp;p5, &amp;p6); printf(&quot;%.3f\\n&quot;,solve(p1, p5, p1)); printf(&quot;%.3f\\n&quot;,solve(p3, p1, p3)); printf(&quot;%.3f\\n&quot;,solve(p2, p6, p2)); printf(&quot;%.3f\\n&quot;,solve(p2, p4, p3)); printf(&quot;%.3f\\n&quot;,solve(p5, p3, p2)); return 0; } 示例代码 - 3 把数组下标作为函数参数，很醒目，数组是全局的。 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int x[10]; void solve(int a, int b, int c) { double p = 1.0 * (x[a] + x[b] + x[c]) / 2; double s = p * (p - x[a]) * (p - x[b]) * (p - x[c]); printf(&quot;%.3f\\n&quot;, sqrt(s)); return; } int main() { for(int i = 1; i &lt;= 6; i++){ scanf(&quot;%d&quot;,&amp;x[i]); } solve(1,5,1); solve(3,1,3); solve(2,6,2); solve(2,4,3); solve(5,3,2); return 0; } C A129 难度 考点 1 递归 题目分析 照葫芦画瓢，把斐波那契数列的代码修改一下，符合这道题的公式即可。 注意要开 long long，函数的返回值类型最好也和调用函数的地方所需要的类型保持一致。 示例代码 #include &lt;stdio.h&gt; long long f(int n) { if(n == 1) { return 1; } else if(n == 2) { return 2; } else { return 2LL * f(n-1) + f(n-2); } } int main() { int x; scanf(&quot;%d&quot;, &amp;x); printf(&quot;%lld\\n&quot;, f(x)); return 0; } 扩展 这道题为了降难度，去掉了记忆化和取模两个知识点。感兴趣的同学可以去挑战一下原题：1788:Pell数列 记忆化：如果把这道题改成多组数据，我们可能会遇到重复计算的问题：比如先计算了 f(4)，随后又要求计算 f(5)，那么在计算 f(5) 的过程中，我们肯定再次会遇到 f(4) 的计算。如果我们把 f(4) 的计算值保存下来，下次计算的时候就可以直接调用了，对吧？ 取模：这一数列的第 26 项开始就超过了 int 范围，不久它也会超过 long long 范围，为了得出较为理想的结果（不溢出），我们要对每一项进行取模。如何在计算的过程中取模？做过 9 次上机+练习赛的你们，应该对这个知识点不会太陌生吧…… D 林士谔算法 航类2024ver 难度 考点 2 函数调用 题目分析 按照题干指引敲代码就行，注意数组读入顺序和 void 类型函数的调用方法。 示例代码 #include&lt;stdio.h&gt; #include&lt;string.h&gt; double a[20]; double p,q; void Shie(int n); int main(){ /*------下面根据指引写你自己的代码------*/ //定义int型变量n int n; //读取n scanf(&quot;%d&quot;,&amp;n); //从第n项开始，一直到第0项，读取全局数组a for(int i = n;i &gt;= 0; i--){ scanf(&quot;%lf&quot;,&amp;a[i]); } //调用Shie函数 Shie(n); //输出全局变量p q printf(&quot;%.6f %.6f&quot;,p,q); return 0; } /*--下面是Shie函数，只要调用即可，无需理解原理--*/ void Shie(int n) { double eps = 1e-12; double b[20]; double c[20]; // 数组 b 是多项式 a 除以当前迭代二次三项式的商 memset(b, 0, sizeof(b)); // 数组 c 是多项式 b 乘以 x 平方再除以当前迭代二次三项式的商 memset(c, 0, sizeof(c)); double dp = 1; double dq = 1; while (dp &gt; eps || dp &lt; -eps || dq &gt; eps || dq &lt; -eps) { double p0 = p; double q0 = q; b[n - 2] = a[n]; c[n - 2] = b[n - 2]; b[n - 3] = a[n - 1] - p0 * b[n - 2]; c[n - 3] = b[n - 3] - p0 * b[n - 2]; int j; for (j = n - 4; j &gt;= 0; j--) { b[j] = a[j + 2] - p0 * b[j + 1] - q0 * b[j + 2]; c[j] = b[j] - p0 * c[j + 1] - q0 * c[j + 2]; } double r = a[1] - p0 * b[0] - q0 * b[1]; double s = a[0] - q0 * b[0]; double rp = c[1]; double sp = b[0] - q0 * c[2]; double rq = c[0]; double sq = -q0 * c[1]; dp = (rp * s - r * sp) / (rp * sq - rq * sp); dq = (r * sq - rq * s) / (rp * sq - rq * sp); p += dp; q += dq; } return; } E 肆佰捌拾陆 难度 考点 2 函数 题目分析 ​ 此题考察了基本的函数应用，只需在程序中定义 f,gf, gf,g 两个函数，并按照题意计算即可。 ​ 注意要开 long long 。 示例代码 #include &lt;stdio.h&gt; int a0 = 123, a1 = 132, a2 = -213; int b0 = 231, b1 = -312, b2 = 321; long long f(long long x) { return a0 + a1 * x + a2 * x * x; } long long g(long long x) { return b0 + b1 * x + b2 * x * x; } int main() { int n = 0; scanf(&quot;%d&quot;, &amp;n); printf(&quot;%lld %lld %lld %lld\\n&quot;, f(f(n)), g(g(n)), f(g(n)), g(f(n))); return 0; } F 云枫的林间小屋 难度 考点 2 循环 题目分析 考虑本题数据范围在10610^6106，每一次从 TTT 循环到 111 一定会 TLE，因此考虑 nnn 的取值范围 n∈[1,T]n\\in[1,\\sqrt{T}]n∈[1,T​] 。 示例代码 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main() { int x, T; scanf(&quot;%d&quot;, &amp;x); for (int i = 0; i &lt; x; ++i) { scanf(&quot;%d&quot;, &amp;T); for (int j = sqrt(T) + 1; j &gt;= 1; --j) { if (T % (j * j) == 0) { printf(&quot;%d %d\\n&quot;, j, T / (j * j)); break; } } } return 0; } Gprime date 难度 考点 3 函数 题目分析 本题给出一个八位日期，求其是否是一个合法的质数日期。 由于数据范围较小，判断素数时可以使用暴力判断，即遍历 2∼n2\\sim \\sqrt n2∼n​ ，看是否有因数。 std 代码使用函数来简化逻辑表达。这样可以使代码的层次结构更加清晰。 代码 #include&lt;stdio.h&gt; #include&lt;math.h&gt; int date; int isPrime(int x){//判断 x 是否是质数 for(int i=2;i&lt;=sqrt(x);++i) if(x%i==0) return 0; return 1; } int isLeapYear(int x){//判断 x 是否是闰年 if(x%4==0&amp;&amp;x%100!=0||x%400==0) return 1; return 0; } int month[13]={0,31,28,31,30,31,30,31,31,30,31,30,31}; int isDate(int x){//判断 x 是否是一个合法日期 int y=x/10000, m=(x%10000)/100, d=x%100; if(m&gt;12||m==0) return 0; month[2]=28+isLeapYear(y); if(d==0||d&gt;month[m]) return 0; return 1; } int T; int main(){ scanf(&quot;%d&quot;,&amp;T); while(T--){ scanf(&quot;%d&quot;,&amp;date); if(isPrime(date)&amp;&amp;isDate(date)) printf(&quot;yes\\n&quot;); else printf(&quot;no\\n&quot;); } return 0; } H 简单的多项式计算 难度 考点 4 递归 题目分析 根据 Hint 的内容，我们可以看出，最内层的多项式可以看成零次的 an−1a_{n-1}an−1​ ，往外一层便是一次多项式 an−1x+an−2a_{n-1}x+a_{n-2}an−1​x+an−2​ ，以此类推，因此，我们可以设 fi(x)f_i(x)fi​(x) 为次数为 iii 的那一层的多项式，可以得出 f0(x)=an−1, f1(x)=an−1x+an−2, fn−1(x)=f(x)f_0(x)=a_{n-1},~f_1(x)=a_{n-1}x+a_{n-2},~f_{n-1}(x)=f(x)f0​(x)=an−1​, f1​(x)=an−1​x+an−2​, fn−1​(x)=f(x) ，所以不难发现，fi(x)f_i(x)fi​(x) 存在递推关系 fi(x)=xfi−1(x)+an−i−1f_i(x)=xf_{i-1}(x)+a_{n-i-1}fi​(x)=xfi−1​(x)+an−i−1​，为了方便，我们可以人为规定 f−1(x)=0f_{-1}(x)=0f−1​(x)=0 ，这样，可以用递归函数解决。 其中需要注意的是，由于递归函数是从高次向低次进入，而最高次对应的常数 a0a_0a0​ 是最后一个输入的，所以应该在输入之前先进入下一层递归函数，然后再输入数据。 同时还应注意数据范围，应当使用 long long 来储存数据。 示例代码 #include &lt;stdio.h&gt; #define P (long long)(1e9+7) long long x; long long f(int n){ if(n&lt;0){ return 0; } long long res=x*f(n-1); long long a; scanf(&quot;%lld&quot;,&amp;a); return (res+a)%P; } int main(){ int t; scanf(&quot;%d&quot;,&amp;t); while(t--){ int n; scanf(&quot;%d%lld&quot;,&amp;n,&amp;x); printf(&quot;%lld\\n&quot;,f(n-1)); } return 0; } I 格雷码！ 难度 考点 5 递归 题目分析 本题要求求出 nnn 位格雷码中的 kkk 号二进制串。 标准的递归模板题，按照规则，nnn 位格雷码，根据 kkk 的大小不同分类： 如果 k&lt;2n−1k&lt; 2^{n-1}k&lt;2n−1 ，则其等于第 kkk 号 n−1n-1n−1 位格雷码前面拼接 000 。 如果 k≥2n−1k\\ge 2^{n-1}k≥2n−1，则其等于第 2n−1−k−12^{n-1}-k-12n−1−k−1 号 n−1n-1n−1 位格雷码前面拼接 111。 递归即可。 代码 #include&lt;stdio.h&gt; long long n,k,pow_2[66]; void solve(long long x,long long p){// 求 x 号 p 位格雷码 if(p==1){ printf(&quot;%lld&quot;,x); return; } if(x&gt;=pow_2[p-1]){ printf(&quot;1&quot;); solve(pow_2[p]-x-1,p-1); } else{ printf(&quot;0&quot;); solve(x,p-1); } } int main(){ scanf(&quot;%lld%lld&quot;,&amp;n,&amp;k); pow_2[0]=1; for(int i=1;i&lt;=60;++i) pow_2[i]=pow_2[i-1]*2; //预处理 pow(2,n) solve(k,n); return 0; } J 上帝与希儿旗 难度 考点 5（含提示）、7（不含提示） 递归 题目分析 我们先来分析一下过程： x=2x=2x=2 时，f(0)=1,f(1)=21=2,f(2)=22=4,f(3)=24=16f(0)=1,f(1)=2^1=2,f(2)=2^2=4,f(3)=2^4=16f(0)=1,f(1)=21=2,f(2)=22=4,f(3)=24=16,f(4)=216=65536,f(5)=265536…f(4)=2^{16}=65536,f(5)=2^{65536}\\dotsf(4)=216=65536,f(5)=265536… 可见，至多 666 天，f(x)f(x)f(x) 就会变成一个远超 long long 范围的巨大的数。因此，我们无法暴力通过快速幂求解。 我们考虑扩展欧拉定理：f(3650) mod p=xf(3649) mod pf(3650)\\ \\text{mod}\\ p = x^{f(3649)}\\ \\text{mod}\\ pf(3650) mod p=xf(3649) mod p ，我们知道 f(3649)f(3649)f(3649) 是一个远大于 ppp 和 φ(p)\\varphi(p)φ(p) 的数。由欧拉函数的定义，我们又知道 p&gt;φ(p)p&gt;\\varphi(p)p&gt;φ(p) 。由扩展欧拉定理，我们又可得到f(3650) mod p=xf(3649) mod φ(p) mod pf(3650)\\ \\text{mod}\\ p = x^{f(3649)\\ \\text{mod}\\ \\varphi(p)\\ }\\ \\text{mod}\\ pf(3650) mod p=xf(3649) mod φ(p) mod p 或者 f(3650) mod p=xf(3649) mod φ(p)+φ(p) mod pf(3650)\\ \\text{mod}\\ p = x^{f(3649)\\ \\text{mod}\\ \\varphi(p)+\\varphi(p)\\ }\\ \\text{mod}\\ pf(3650) mod p=xf(3649) mod φ(p)+φ(p) mod p 。无论何种情况，$f(3649)\\ \\text{mod}\\ \\varphi(p)\\ $ 和 φ(p)\\varphi(p)φ(p) 以及它们的和都在 long long 范围内，因此可以用快速幂求解。因此，问题转化为求 f(3649) mod φ(p)f(3649)\\ \\text{mod}\\ \\varphi(p)f(3649) mod φ(p) 。要求出这个数，我们又要求出 f(3648) mod φ(φ(p))f(3648)\\ \\text{mod}\\ \\varphi(\\varphi(p))f(3648) mod φ(φ(p)) 和 φ(φ(p))\\varphi(\\varphi(p))φ(φ(p)) ，如此循环往复。 我们可以证明，嵌套 600600600 次以内（具体数值可以自己试试，600600600 这个数字我随便说的，可能实际上也就几十次）大概就可以让模数 φ(φ(…φ(p)))\\varphi(\\varphi(\\dots\\varphi(p)))φ(φ(…φ(p)))变成 111 。任何数模 111 都是 000 。我们以此判断递归结束，就可以依次解决上面的问题。 示例代码 #include&lt;stdio.h&gt; long long gcd(long long a, long long b) { long long temp; while (b != 0) { temp = a % b; a = b; b = temp; } return a; } long long qpow(long long n, long long k, long long m) { long long ans = 1; while (k != 0) { if (k &amp; 1)ans = n * ans % m; k &gt;&gt;= 1; n = n * n % m; } return ans; } long long phi(long long n) { long long ans = n; for (int i = 2; i * i &lt;= n; i++) { if (n % i == 0) { ans = ans / i * (i - 1); while (n % i == 0) { n /= i; } } } if (n &gt; 1)ans = ans / n * (n - 1); return ans; } long long mod(long long x, long long m) { if (m == 1)return 0; if (gcd(x, m) == 1)return qpow(x, mod(x, phi(m)), m); else return qpow(x, mod(x, phi(m)) + phi(m), m); } int main() { long long p, x; scanf(&quot;%lld%lld&quot;, &amp;x, &amp;p); printf(&quot;%lld\\n&quot;, mod(x, p)); return 0; } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C5 - Solution-24航c","link":"https://github.pansis.site/post/C5 - Solution-24航c/","stats":{"text":"17 min read","time":962000,"words":3219,"minutes":17},"dateFormat":"2024-10-24"},{"content":"E3 - Solution A Firefly小姐的位运算大练习—基础版 难度 考点 1 位运算 题目分析 在题目给定的模版中填充计算的部分即可。 示例代码 #include &lt;stdio.h&gt; int main(){ int n; scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { int op; scanf(&quot;%d&quot;, &amp;op); if (op == 1) { unsigned int a, b; scanf(&quot;%u %u&quot;, &amp;a, &amp;b); printf(&quot;%u\\n&quot;, a &amp; b); }else if (op == 2) { unsigned int a, b; scanf(&quot;%u %u&quot;, &amp;a, &amp;b); printf(&quot;%u\\n&quot;, a | b); }else if (op == 3) { unsigned int a, b; scanf(&quot;%u %u&quot;, &amp;a, &amp;b); printf(&quot;%u\\n&quot;, a ^ b); } } } Author:SiSi{\\LARGE {\\color{yellowgreen} \\mathscr{Author:SiSi}}}Author:SiSi B 二进制表示！ 难度 考点 111~222 二进制表示 题目分析 如题所示，我们需要输出原码，反码和补码。 计算机中存储数字使用补码。 原码就是符号位加上真值的绝对值，即用第一位表示符号，其余位表示值。 反码用于简化负数的表示。正数的反码与其原码相同，而负数的反码是其原码的非符号位取反。 补码是计算机存储数字的形式。正数的补码与其原码相同，而负数的补码是其反码加111。 示例代码 #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; int main() { int t,a,b; int n; scanf(&quot;%d&quot;,&amp;n); int m = abs(n); if(m==n){ for(int i=31;i&gt;=0;i--) printf(&quot;%d&quot;,(m&gt;&gt;i)&amp;1); printf(&quot;\\n&quot;); for(int i=31;i&gt;=0;i--) printf(&quot;%d&quot;,(m&gt;&gt;i)&amp;1); printf(&quot;\\n&quot;); for(int i=31;i&gt;=0;i--) printf(&quot;%d&quot;,(m&gt;&gt;i)&amp;1); printf(&quot;\\n&quot;); } else{ printf(&quot;%d&quot;,1); for(int i=30;i&gt;=0;i--) printf(&quot;%d&quot;,(m&gt;&gt;i)&amp;1); printf(&quot;\\n&quot;); printf(&quot;%d&quot;,1); for(int i=30;i&gt;=0;i--) printf(&quot;%d&quot;,!((m&gt;&gt;i)&amp;1)); printf(&quot;\\n&quot;); for(int i=31;i&gt;=0;i--) printf(&quot;%d&quot;,((n&gt;&gt;i)&amp;1)); printf(&quot;\\n&quot;); } return 0; } C 我要吃到最后一块糖果！(Easy Version) 难度 考点 2 位运算 题目分析 由于每次只能拿 222 的幂次个，所以可以把糖果总数 nnn 考虑成二进制，因为需要尽可能得多拿，所以，可以理解成：每一次需要把总数 nnn 的二进制最高位变成 000，考虑一共可以变化几次即可。例如给出的第一个样例： (31)10=(11111)2(31)_{10}=(11111)_2 (31)10​=(11111)2​ 每次拿走最高位的 111 ，一共可以拿 555 次。注意到，555 是奇数，所以 Alice\\text{Alice}Alice 拿走最后一块。 所以本题只需判断 nnn 的二进制表示中 111 的数量的奇偶，进一步分析，只需判断出 nnn 的二进制表示中各位数字之和的奇偶即可。 示例代码 #include &lt;stdio.h&gt; int main(){ int t; scanf(&quot;%d&quot;,&amp;t); while(t--){ unsigned int n; scanf(&quot;%u&quot;,&amp;n); int k=0; while(n){ k=k+(n&amp;1); n=n&gt;&gt;1; } if(k&amp;1){ printf(&quot;Alice\\n&quot;); } else{ printf(&quot;Bob\\n&quot;); //千万不要忘记输出换行！！！！！！ } } return 0; } D 蒙版工具 难度 考点 3 位运算 题目分析 按照题意作答即可。 示例代码 - 1 #include &lt;stdio.h&gt; int main() { unsigned int x, y, mask; while(scanf(&quot;%u%u%u&quot;, &amp;x, &amp;y, &amp;mask) != EOF) { unsigned int res = 0; for(int i = 0; i &lt; 32; ++i) { int bit = (mask &gt;&gt; i) &amp; 1; if(bit == 1) { int bit_y = (y &gt;&gt; i) &amp; 1; if(bit_y == 1) { res |= 1 &lt;&lt; i; } } else { int bit_x = (x &gt;&gt; i) &amp; 1; if(bit_x == 1) { res |= 1 &lt;&lt; i; } } } printf(&quot;%u\\n&quot;, res); } return 0; } 示例代码 - 2 #include &lt;stdio.h&gt; int main() { unsigned int x, y, mask; while(scanf(&quot;%u%u%u&quot;, &amp;x, &amp;y, &amp;mask) != EOF) { unsigned int res = (x&amp;(~mask)|(y&amp;(mask))); printf(&quot;%u\\n&quot;, res); } return 0; } E Firefly小姐的位运算大练习—进阶版 难度 考点 3 位运算 题目分析 在题目给定的模版中填充计算的部分即可。 本题中的位运算操作都是考试常见操作，需要熟练掌握。 示例代码 #include &lt;stdio.h&gt; int main(){ int n; scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { int op; scanf(&quot;%d&quot;, &amp;op); if (op == 1) { // 二进制位1的个数 unsigned int a; scanf(&quot;%u&quot;, &amp;a); int res = 0; // 初始化 for (int i = 0; i &lt; 32; i++) { if ((a &gt;&gt; i &amp; 1) == 1) { // 将a的第i位通过右移移至第一位，再&amp;1将其余部分置0 res++; // 统计 } } printf(&quot;%d\\n&quot;, res); }else if (op == 2) { unsigned int a; int l; scanf(&quot;%u %d&quot;, &amp;a, &amp;l); printf(&quot;%u\\n&quot;, a ^ (1 &lt;&lt; l)); // ^1翻转，^0不变，^(1 &lt;&lt; l)可以达到其余位不变，l位翻转的目的 }else if (op == 3) { unsigned int a; int l; scanf(&quot;%u %d&quot;, &amp;a, &amp;l); printf(&quot;%u\\n&quot;, a &amp; ~(1 &lt;&lt; l)); // &amp;1不变，&amp;0置0，&amp;~(1 &lt;&lt; l)可以达到其余位不变，l位置0的目的 }else if (op == 4) { unsigned int a; int l; scanf(&quot;%u %d&quot;, &amp;a, &amp;l); printf(&quot;%u\\n&quot;, a | (1 &lt;&lt; l)); // |1置1，|0不变，|(1 &lt;&lt; l)可以达到其余位不变，l位置1的目的 }else if (op == 5) { unsigned int a; int l; scanf(&quot;%u %d&quot;, &amp;a, &amp;l); printf(&quot;%u\\n&quot;, a &gt;&gt; l &amp; 1); // 将a的第i位通过右移移至第一位，再&amp;1将其余部分置0 } } } Author:SiSi{\\LARGE {\\color{yellowgreen} \\mathscr{Author:SiSi}}}Author:SiSi F Firefly小姐的位运算大练习—最终版 难度 考点 3 位运算 题目分析 在题目给定的模版中填充计算的部分即可。 本题中的位运算操作都是考试常见操作，需要熟练掌握。 示例代码 #include &lt;stdio.h&gt; int main(){ int n; scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { int op; scanf(&quot;%d&quot;, &amp;op); if (op == 1) { unsigned int a; int l, r; scanf(&quot;%u %d %d&quot;, &amp;a, &amp;l, &amp;r); for (int i = l; i &lt;= r; i++) { // 循环l至r位的单位翻转操作（参考E），下同 a ^= 1 &lt;&lt; i; } printf(&quot;%u\\n&quot;, a); }else if (op == 2) { unsigned int a; int l, r; scanf(&quot;%u %d %d&quot;, &amp;a, &amp;l, &amp;r); for (int i = l; i &lt;= r; i++) { a &amp;= ~(1 &lt;&lt; i); } printf(&quot;%u\\n&quot;, a); }else if (op == 3) { unsigned int a; int l, r; scanf(&quot;%u %d %d&quot;, &amp;a, &amp;l, &amp;r); for (int i = l; i &lt;= r; i++) { a |= 1 &lt;&lt; i; } printf(&quot;%u\\n&quot;, a); }else if (op == 4) { unsigned int a, b; int l, r; scanf(&quot;%u %u %d %d&quot;, &amp;a, &amp;b, &amp;l, &amp;r); for (int i = l; i &lt;= r; i++) { a &amp;= ~(1 &lt;&lt; i); // 将a的第i位空出来 a |= (b &amp; (1 &lt;&lt; i)); // 取出b的第i位，接到a上 } printf(&quot;%u\\n&quot;, a); }else if (op == 5) { unsigned int a; int l, r; scanf(&quot;%u %d %d&quot;, &amp;a, &amp;l, &amp;r); unsigned x = a &gt;&gt; l &amp; 1, y = a &gt;&gt; r &amp; 1; // 取出a的l和r位 a &amp;= ~(1 &lt;&lt; l); // 空出a的第l位 a &amp;= ~(1 &lt;&lt; r); a |= x &lt;&lt; r; a |= y &lt;&lt; l; printf(&quot;%u\\n&quot;, a); } } } Author:SiSi{\\LARGE {\\color{yellowgreen} \\mathscr{Author:SiSi}}}Author:SiSi G s7h的简单异或题 难度 考点 2 位运算 题目分析 本题给定一个数列，多次询问求 lll 到 rrr 的异或和。 首先根据异或的运算律可以推导出一个性质： (a1⊕a2⊕...⊕ar)⊕(a1⊕...⊕al−1)=al⊕al+1...⊕ar(a_1\\oplus a_2\\oplus...\\oplus a_r)\\oplus(a_1\\oplus...\\oplus a_{l-1})=a_l\\oplus a_{l+1} ... \\oplus a_r(a1​⊕a2​⊕...⊕ar​)⊕(a1​⊕...⊕al−1​)=al​⊕al+1​...⊕ar​。 然后类比 E2 J ，构建一种类似前缀和的 “前缀异或和” 算法即可。 代码 #include&lt;stdio.h&gt; int n,m,a[200050],l,r,sum[200050]; int main(){ scanf(&quot;%d%d&quot;,&amp;n,&amp;m); for(int i=1;i&lt;=n;++i){ scanf(&quot;%d&quot;,&amp;a[i]); sum[i]=a[i]^sum[i-1]; } for(int i=1;i&lt;=m;++i){ scanf(&quot;%d%d&quot;,&amp;l,&amp;r); printf(&quot;%d\\n&quot;,sum[r]^sum[l-1]); } return 0; } H 小霁的时间乱流plus+++ 难度 考点 4 数组 循环 题目分析 本题在分钟进位小时，小时进位天数较为简单。 但在天数进位月份时，需要考虑每月天数不同也需要考虑闰年的因素。 闰年的规定是 “能被400整除，或者能被4整除但不能被100整除的是闰年”，转化为C语言就是((y % 4 == 0 &amp;&amp; y % 100 != 0) || y % 400 == 0)，如果该条件式成立，则y是闰年，反之不是闰年。 具体的乱流中的时间调整方式已在题目中详细给出，此处不再赘述。 示例代码 #include&lt;stdio.h&gt; int main() { int months[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};//定义每月天数 int n;//数据组数 int y,mon,d,h,min;//年、月、日、时、分 int tempday; scanf(&quot;%d&quot;,&amp;n); for (int i = 0; i &lt; n; ++i) { scanf(&quot;%d.%d.%d %d:%d&quot;,&amp;y,&amp;mon,&amp;d,&amp;h,&amp;min);//输入乱流中的时间 h+=(min/60); min%=60; d+=(h/24); h%=24;//计算正常的小时数、分钟数，并向天数进位 y += (mon - 1) / 12; mon = (mon - 1) % 12 + 1;//先将月份向年份进位 tempday=months[mon]+(((y % 4 == 0 &amp;&amp; y % 100 != 0) || y % 400 == 0)&amp;&amp;(mon==2));//计算当前的y和mon对应的当月天数 //(((y % 4 == 0 &amp;&amp; y % 100 != 0) || y % 400 == 0)&amp;&amp;(mon==2))用于判定当前的y和mon是否是闰年的2月，若条件式成立，则条件式的值为1，在28天的基础上加1天。 while (d&gt;tempday)//如果天数超过该月份的天数 { d-=tempday; mon++;//天数减去该月天数，月份加一 y += (mon - 1) / 12; mon = (mon - 1) % 12 + 1;//若月份大于12，则向年份进位 tempday=months[mon]+(((y % 4 == 0 &amp;&amp; y % 100 != 0) || y % 400 == 0)&amp;&amp;(mon==2));//计算当前的y和mon对应的当月天数 } printf(&quot;%04d.%02d.%02d %02d:%02d\\n&quot;,y,mon,d,h,min); } } I -M3rcury-的解方程组 难度 考点 5 位运算 题目分析 我们观察异或运算对应的真值表： aaa bbb a⊕ba \\oplus ba⊕b 0 0 0 0 1 1 1 0 1 1 1 0 可以发现，在二进制对应的位上，异或运算和加法运算的结果一样，只是忽略了进位。 那么，对于 x+yx + yx+y 和 x⊕yx \\oplus yx⊕y 来说，他们的差值就是中间产生进位的值。 只有 xxx 和 yyy 对应位都为 111 时，才会在高一位的地方产生进位。 因此我们可以得到 x+y=(x⊕y)+2∗(x&amp;y)x + y = (x \\oplus y) + 2 * (x \\&amp; y) x+y=(x⊕y)+2∗(x&amp;y) 所以 x&amp;y=(x+y)−(x⊕y)2x \\&amp; y = \\frac{(x + y) - (x \\oplus y)}{2} x&amp;y=2(x+y)−(x⊕y)​ 由于 $x \\geq x &amp; y $ ，因此 x&amp;yx \\&amp; yx&amp;y 即为 xxx 的最小值， a−xa - xa−x 即为对应的 yyy 最后考虑无解的情况。 因为这样求出的 yyy 不一定满足 x⊕y=bx \\oplus y = bx⊕y=b ，因此需要代回去验证 此外 a&lt;ba&lt;ba&lt;b 的情况也是无解的 示例代码 #include &lt;stdio.h&gt; int main() { unsigned long long a,b; scanf(&quot;%llu%llu&quot;,&amp;a,&amp;b); if(a&lt;b) { printf(&quot;-1\\n&quot;); } else { unsigned long long x=a-b&gt;&gt;1; unsigned long long y=a-x; if((x^y)!=b) printf(&quot;-1\\n&quot;); else printf(&quot;%llu %llu\\n&quot;,x,y); } return 0; } J IEEE 754科学 难度 考点 5 数据类型 题目分析 本题分为两部分：构造浮点数和输出浮点数的二进制表示。 对于构造最大的浮点数，我们直接令 S=0S=0S=0 ，构造最大的 EEE 和 TTT 。易知最大的 EEE 为 204620462046 ，其二进制表示为 11111111110 ，最大的 TTT 为 1.11111...111 （小数点后 525252 个 1 ）。因此最大的浮点数的二进制表示为 0111111111101111111111111111111111111111111111111111111111111111 。我们创造这个二进制表示对应的 unsigned long long 类型变量，将这个变量逐位地拷贝到一个 double 型变量再输出浮点数即可。特别的，我们可以利用 0x 前缀表示这是一个16进制数。这个浮点数对应的十六进制表示是 0x7fefffffffffffff 。 同理，对于构造最小的正浮点数，我们直接令 S=0S=0S=0 ，构造最小的 EEE 和 TTT 。最小的 EEE 是 000 ，此时 21−bias2^{1-bias}21−bias 最小，构造 TTT 为 0.000000...001 （小数点和 1 之间 515151 个 000 ）。因此最大的浮点数的二进制表示为 0000000000000000000000000000000000000000000000000000000000000001。我们创造这个二进制表示对应的 unsigned long long 类型变量，将这个变量逐位地拷贝到一个 double 型变量再输出浮点数即可。这个 unsigned long long 类型变量显然是 111 。 拷贝方法如下： unsigned long long u = 0x7fefffffffffffff;//u = 1 double a; memcpy(&amp;a,&amp;u,sizeof(double));//u拷贝到a 对于输出浮点数的二进制表示，读入后参考 Hint 拷贝，然后位运算逐位输出即可，不再赘述。 示例代码 - 1 #include&lt;stdio.h&gt; #include&lt;string.h&gt; int main() { unsigned long long d; double a; d = 0x7FEFFFFFFFFFFFFF; memcpy(&amp;a, &amp;d, sizeof(double)); printf(&quot;%.2f\\n&quot;, a); d = 1; memcpy(&amp;a, &amp;d, sizeof(double)); printf(&quot;%.1100f\\n&quot;, a); int m; scanf(&quot;%d&quot;, &amp;m); for (int i = 0; i &lt; m; i++) { scanf(&quot;%lf&quot;, &amp;a); memcpy(&amp;d, &amp;a, sizeof(double)); for (int j = 63; j &gt;= 0; j--) { printf(&quot;%llu&quot;, (d &gt;&gt; j) &amp; 1); } printf(&quot;\\n&quot;); } return 0; } 示例代码 - 2 推荐大家学了指针以后再来体会这份代码。 #include&lt;stdio.h&gt; int main() { unsigned long long d; d = 0x7FEFFFFFFFFFFFFF; printf(&quot;%.2f\\n&quot;, *(double *)(&amp;d)); d = 1; printf(&quot;%.1100f\\n&quot;, *(double *)(&amp;d)); int m; scanf(&quot;%d&quot;, &amp;m); for (int i = 0; i &lt; m; i++) { scanf(&quot;%lf&quot;, (double *)(&amp;d)); for (int j = 63; j &gt;= 0; j--) { printf(&quot;%llu&quot;, (d &gt;&gt; j) &amp; 1); } printf(&quot;\\n&quot;); } return 0; } 示例代码 - 3 推荐大家学了联合体（不是结构体）以后再来体会这份代码。 #include&lt;stdio.h&gt; union{ double a; unsigned long long d; }num; int main() { num.d = 0x7FEFFFFFFFFFFFFF; printf(&quot;%.2f\\n&quot;, num.a); num.d = 1; printf(&quot;%.1100f\\n&quot;, num.a); int m; scanf(&quot;%d&quot;, &amp;m); for (int i = 0; i &lt; m; i++) { scanf(&quot;%lf&quot;, &amp;num.a); for (int j = 63; j &gt;= 0; j--) { printf(&quot;%llu&quot;, ((num.d) &gt;&gt; j) &amp; 1); } printf(&quot;\\n&quot;); } return 0; } K Firefly!!! 难度 考点 3 空白字符，字符串匹配 题目分析 本题的考点在于利用空白字符（'\\n'，EOF）判断输入的字符画和标准字符画是否相同。 我们可以先统计第一个空行出现前读入的 f 个数确定需要匹配的标准 F\\text{F}F 型字符画 尺寸，再一一进行匹配。 示例代码 #include &lt;stdio.h&gt; int main() { int n = 0; while (getchar() != '\\n') { // F型字符画尺寸 n++; } for (int i = 0; i &lt; n - 2; i++) { // 判断接下来是否是连续n-2行每行一个字符f（即连续读入n-2个'f'和'\\n'） char a, b; a = getchar(); b = getchar(); if (!(a == 'f' &amp;&amp; b == '\\n')) { // 匹配失败，输出No并使用return 0直接终止程序 printf(&quot;No&quot;); return 0; } } for (int i = 0; i &lt; n; i++) { // 判断接下来是否是连续n个字符f char a; a = getchar(); if (a != 'f') { printf(&quot;No&quot;); return 0; } } for (int i = 0; i &lt; n - 1; i++) { // 判断接下来是否是连续n-1行每行一个字符f（即连续读入n-1个'\\n'和'f'） char a, b; a = getchar(); b = getchar(); if (!(a == '\\n' &amp;&amp; b == 'f')) { printf(&quot;No&quot;); return 0; } } char c; c = getchar(); if (c == EOF) { // 判断是否已经读到字符画末尾，即后面没有更多字符 printf(&quot;Yes %d&quot;, n); }else printf(&quot;No&quot;); } Author:SiSi{\\LARGE {\\color{yellowgreen} \\mathscr{Author:SiSi}}}Author:SiSi ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"E3 - Solution-24航c","link":"https://github.pansis.site/post/E3 - Solution-24航c/","stats":{"text":"19 min read","time":1126000,"words":3861,"minutes":19},"dateFormat":"2024-10-24"},{"content":"位运算基本算式 如果同学们难以理解或实现位运算的各种复杂构造，建议大家先掌握下面三个位运算，然后结合条件和循环实现更复杂的位运算操作。 1、取值 (n&gt;&gt;i)&amp;1 //提取n的第i位 示例 打印n的补码（int 类型） #include&lt;stdio.h&gt; int main(){ int n; scanf(&quot;%d&quot;,&amp;n); for (int i = 31; i &gt;=0; --i) { printf(&quot;%d&quot;,(n&gt;&gt;i)&amp;1);//提取n的第i位 } } 打印n的补码（long long 类型） #include&lt;stdio.h&gt; int main(){ long long n; scanf(&quot;%lld&quot;,&amp;n); for (int i = 63; i &gt;=0; --i) { printf(&quot;%lld&quot;,(n&gt;&gt;i)&amp;1);//提取n的第i位 } } 2、置1 n=n|(1&lt;&lt;i); //对n的第i位置1 示例 对n的第l到r位置1 #include&lt;stdio.h&gt; int main(){ int n; int l,r; scanf(&quot;%d&quot;,&amp;n); scanf(&quot;%d%d&quot;,&amp;l,&amp;r); //对n的第l到r位置1 for (int i = l; i &lt;=r; ++i) { n|=(1&lt;&lt;i); } printf(&quot;%d\\n&quot;,n); } 3、置0 n=n&amp;(~(1&lt;&lt;i)); //对n的第i位置0 示例 对n的第l到r位置0 #include&lt;stdio.h&gt; int main(){ int n; int l,r; scanf(&quot;%d&quot;,&amp;n); scanf(&quot;%d%d&quot;,&amp;l,&amp;r); //对n的第l到r位置0 for (int i = l; i &lt;=r; ++i) { n&amp;=~(1&lt;&lt;i); } printf(&quot;%d\\n&quot;,n); } 4、打包的函数 int inplace01(int a,int n,int x) //替换二进制特定位的值,将a的第n位替换成x //a为要转化的数字，x为替换的值（0或1），n为位数 {//注意返回值和参数类型，要改成相应的类型，而且1ll也要改成相应的类型（unsigned int是1u，long long 是1ll，int是1） if (x==0) a &amp;= (~(1 &lt;&lt; n)); //将a的二进制第n位替换为0 else a|=(1&lt;&lt;n); //将a的二进制第n位替换为1 return a; } int get01(int a,int n) //提取二进制的特定位，提取a的第n位 //a为提取的数字，n为位数 {//注意返回值和参数类型，要改成相应的类型，而且1ll也要改成相应的类型（unsigned int是1u，long long 是1ll，int是1） return a&amp;(1&lt;&lt;n); //取出a的第n位，返回（若为1，则返回2^n,否则a=0） } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"位运算基本算式-24航c","link":"https://github.pansis.site/post/位运算基本算式-24航c/","stats":{"text":"3 min read","time":159000,"words":562,"minutes":3},"dateFormat":"2024-10-18"},{"content":"问题1：数据类型搞不清楚 1、int+int爆int问题 int a=1999999999; int b=1999999999; printf(&quot;%d\\n&quot;,a+b); //错 printf(&quot;%lld\\n&quot;,a+b); //错 printf(&quot;%lld\\n&quot;,1ll*a+b); //对 printf(&quot;%lld\\n&quot;,(long long)a+b); //对 printf(&quot;%lld\\n&quot;,(long long)(a+b); //错 2、整除问题 除的时候用整除 int a=2; int b=3; double c=a/b; //得到的是0 double c2=1.0*a/b; //得到的是0.6666666 double c3=(double)a/b; //得到的是0.6666666 double c4=(double)(a/b); //得到的是0.00 2、printf、scanf类型对应错误 %d： － 以十进制方式输出带标记整数（正数默认不输出标记）； int 十进制 %u： － 以十进制方式输出无标记整数； unsigned int 十进制 %lf/%f(输入/输出) double 小数方式 %c： － 输出单个字符； char %lld -以十进制方式输入长整型 long long %llu 以十进制方式输出unsigned long long unsigned long long 问题2：未赋值就计算 int main() { int a,b,c; c=a+b; scanf(&quot;%d%d&quot;,&amp;a,&amp;b); printf(&quot;%d\\n&quot;,c); } 问题3：位运算优先级问题 1、&amp;和== if((n&gt;&gt;l)&amp;1==0) //错误 if(((n&gt;&gt;l)&amp;1)==0) //正确 解决方法：该加括号加括号 2、 p = (n &gt;&gt; h) &amp; 1 + p; p = (n &gt;&gt; h) &amp; 1 + p; 问题4：整数范围内滥用pow pow的输出值是double，且超过一定范围会导致误差超过1.从而导致计算错误。 1&lt;&lt;i 的值是2的n次方 问题5：把^当做乘方 问题6：循环时的i搞不清范围 比如输出数的原码补码反码 for (int i = 31; i &gt;=0 ; --i) //对，i从31到0 for (int i = 32; i &gt;=0 ; --i) //错，i从32到0，循环了33次 数组下标循环时，前后对应不上 int main() { int n; int a[100]; int num=0; scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { scanf(&quot;%d&quot;, &amp;a[i]); } for (int i = 1; i &lt; n; i++) { num+=a[i]; } } 问题7：每组数据之间的累加变量不重置或不初始化 E3-C 我要吃到最后一块糖果！(Easy Version) #include &lt;stdio.h&gt; int main(void) { int i,t,m,count; count=0; scanf(&quot;%d&quot;,&amp;t); unsigned int n; for(i=1;i&lt;=t;i++) { scanf(&quot;%u&quot;,&amp;n); for(m=0;m&lt;=32;m++) {if(n&amp;1==1) { n&gt;&gt;=1; count++;//累加变量未重置为0 } else if(n&amp;1==0) n&gt;&gt;=1; } if(count%2==1) printf(&quot;Alice\\n&quot;); if(count%2==0) printf(&quot;Bob\\n&quot;); } } 问题8：将&gt;&gt;或&lt;&lt;当成&gt;&gt;=或&lt;&lt;=用 以倒序显示补码为例 int main() { int n; scanf(&quot;%d&quot;,&amp;n); for (int i = 0; i &lt; 32; ++i) { printf(&quot;%d&quot;,n&amp;1); n&gt;&gt;1;//错 } } 问题9：！和~分不清 int main() { printf(&quot;%d %d&quot;,~1,!1); } 运行结果 -2 0 错误示例 问题10：滥开不定长数组 #include &lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;,&amp;n); int a[n];//错 //定义数组时，[]只能填常数 } 错误原因：本身C99标准支持不定长数组，因此在本地运行时，使用不定长数组可以正常运行，是因为本地测试n的数值通常很小。但OJ测试时，n的值可能很大，直接造成爆栈，从而错误。 问题11：位运算使用极其复杂的构造，企图一个式子出结果 不好的示例 #include &lt;stdio.h&gt; int main() { int n, op; scanf(&quot;%d&quot;, &amp;n); unsigned int a, l, x; for (int i = 0; i &lt; n; i++) { scanf(&quot;%d&quot;, &amp;op); if (op == 1) { scanf(&quot;%u&quot;, &amp;a); x = 0; for (int m = 31; m &gt;= 0; m--) { if (((a &gt;&gt; m) &amp; 1) == 1) { x += 1; } } printf(&quot;%u\\n&quot;, x); } if (op == 2) { scanf(&quot;%u%u&quot;, &amp;a, &amp;l); x = (((a &gt;&gt; (32 - l)) ^ 1) &lt;&lt; (32 - l)) + ((a &lt;&lt; l) &gt;&gt; l); printf(&quot;%u\\n&quot;, x); } if (op == 3) { scanf(&quot;%u%u&quot;, &amp;a, &amp;l); x = (((a &gt;&gt; (32 - l)) &amp; 0) &lt;&lt; (32 - l)) + ((a &lt;&lt; l) &gt;&gt; l) + ((a &gt;&gt; (33 - l)) &lt;&lt; (33 - l)); printf(&quot;%u\\n&quot;, x); } if (op == 4) { scanf(&quot;%u%u&quot;, &amp;a, &amp;l); x = (((a &gt;&gt; (32 - l)) | 1) &lt;&lt; (32 - l)) + ((a &lt;&lt; l) &gt;&gt; l); printf(&quot;%u\\n&quot;, x); } if (op == 5) { scanf(&quot;%u%u&quot;, &amp;a, &amp;l); x = (a &gt;&gt; (32 - l)) &amp; 1; printf(&quot;%u\\n&quot;, x); } } return 0; } 建议只用取值、置1、置0三种简单的位运算再加各种循环和条件实现复杂的位运算 问题12：scanf里面滥加\\n ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"常见问题-24航c","link":"https://github.pansis.site/post/常见问题-24航c1/","stats":{"text":"6 min read","time":330000,"words":1106,"minutes":6},"dateFormat":"2024-10-18"},{"content":"C4 - Solution A Firefly小姐的另一道水题 难度 考点 1 浮点数 题目分析 使用语句 fabs((a + b) - c) &lt; 1e-9 判断两个浮点数是否相等即可，由于浮点数精度问题，不可以使用形如 a + b == c 的语句判断相等，会因为浮点数精度误差导致错误。 示例代码 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main(){ double a, b, c; while (scanf(&quot;%lf %lf %lf&quot;, &amp;a, &amp;b, &amp;c) != EOF) { if (fabs(a + b - c) &lt; 1e-9) { printf(&quot;Yes\\n&quot;); }else { printf(&quot;No\\n&quot;); } } return 0; } Author:SiSi{\\LARGE {\\color{yellowgreen} \\mathscr{Author:SiSi}}}Author:SiSi B 算个乘方（水题速来） 难度 考点 1 循环 题目分析 对于每组数据，先初始化一个变量为 1，然后对这个变量每次乘以 nnn 即可。 示例代码 #include &lt;stdio.h&gt; int main() { long long n, k; while (scanf(&quot;%lld%lld&quot;, &amp;n, &amp;k) != EOF) { long long ans = 1; for (int i = 1; i &lt;= k; i++) { ans = ans * n; } printf(&quot;%lld\\n&quot;, ans); } return 0; } C 山 难度 考点 1 循环， 控制 题意分析 “找规律”是一种用途颇广的方法。我们不妨动用聪明才智，找出每行输出的内容和该行序号的关系，并用C语言明确地表述出来，以便计算机（和我们不同，并不具备任何聪明才智）遵照执行。就本题而言： 不妨将待输出的各行从上至下依次编号为 1,2,...,n1,2,...,n1,2,...,n。 注意到第 nnn 行 有 000 个空格， 而行的编号每减一，空格数量就加一。易得第 iii 行有 −i+n-i+n−i+n 个空格。 注意到第 111 行有 111 个 'A'， 而行的编号每加一， 'A'的数量就加二。易得第 xxx 行有 2∗i−12*i-12∗i−1 个'A'。 用循环实现上述过程。 示例代码 #include&lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;,&amp;n); for (int i=1;i&lt;=n;i++) //for语言的写法应当使i的变化方式和我们的设想相符 { for (int j=1;j&lt;=-i+n;j++) //如上述，第i行应该输出-i+n个空格 printf(&quot; &quot;); for (int j=1;j&lt;=2*i-1;j++) //第i行应该输出2*i-1个'A' printf(&quot;A&quot;); printf(&quot;\\n&quot;); //以回车结束这一行 } return 0; } D Deadline 的艺术（复活版） 难度 考点 2 日期时间计算 题目分析 照着公式搓就行。坑点有： 不定组数据输入。 如果是 xxx 年的 1、2 月份，要转换成 x−1x-1x−1 年 13、14 月份去计算。 DDD 有可能是负数，D mod 7D \\bmod 7Dmod7 依然是负数。所以要模 7 之后再加 7，各位可以数学验证其合理性。 这些坑点都提示到了，所以这题不难吧。 示例代码 #include &lt;stdio.h&gt; int main() { int year, month, day; while(scanf(&quot;%4d%2d%2d&quot;, &amp;year, &amp;month, &amp;day) != EOF) { //%4d是表示读入一个有四个字符的数字，%2d是表示读入一个有两个字符的数字 if(month &lt; 3) { month += 12; year -= 1; } int c = year / 100; int y = year % 100; int D = c / 4 - 2 * c + y + y / 4 + (13 * (month + 1) / 5) + day - 1; int W = (D % 7 + 7) % 7; switch(W) { case 0: printf(&quot;Sunday&quot;); break; case 1: printf(&quot;Monday&quot;); break; case 2: printf(&quot;Tuesday&quot;); break; case 3: printf(&quot;Wednesday&quot;); break; case 4: printf(&quot;Thursday&quot;); break; case 5: printf(&quot;Friday&quot;); break; case 6: printf(&quot;Saturday&quot;); break; } printf(&quot;\\n&quot;); } return 0; } 补充说明 再碎碎念一下，各位在不定组输入的时候，一定要注意每次给局部变量赋初始值的问题，一定要注意（如果需要）每组记得输出换行或空格的问题！样例代码中使用的是 switch-case 结构语句，在类似这样的情形时有很好的代码组织效果。但是一定记得在每个 case 后加 break！ 或者可以使用字符常量数组来存储 7 个单词，然后将 W 作为下标进行访问输出。因为二维数组还没学，这个做法就不展示了，感兴趣的同学可以自己根据课本尝试，有问题问你的助教。 E 开幕式 难度 考点 2 辗转相除 gcdgcdgcd 题目分析 分析题意不难发现，每组人数必须是所有班级人数的公因数，用 总人数 ÷\\div÷ 每组人数 即可求出总组数。 因此用一个 num 变量记录 nnn 个班级的总人数，再用一个变量 gcd 循环求取各班人数的最大公因数 gng_ngn​。 示例代码 #include&lt;stdio.h&gt; int main() { int n,gcd,a; scanf(&quot;%d%d&quot;,&amp;n,&amp;gcd); //g1就是第一个数本身 int num=gcd; for(int i=1;i&lt;n;i++) //后n-1个数，只循环n-1次 { scanf(&quot;%d&quot;,&amp;a); num+=a; //累加总人数 while(a!=0) //辗转相除 { int temp=gcd%a; gcd=a; a=temp; } } printf(&quot;%d&quot;,num/gcd); //总人数除以最大公因数 return 0; } F Ma5线 难度 考点 3 循环，数组 题目分析 按照规则计算即可。可分为以下几步： 从第一天开始，读入每一天的数据 从第五天开始，计算每一天的五日均值 从第七天开始，找每一天是否符合条件，符合条件则退出，不符合条件则继续循环。 示例代码 - 1 #include &lt;stdio.h&gt; int main() { int t; scanf(&quot;%d&quot;, &amp;t); for (int i = 1; i &lt;= t; i++) { int a[109];//存储原始数据 double b[109] = {0};//存储五日均值 int n; scanf(&quot;%d&quot;, &amp;n); for (int j = 1; j &lt;= n; j++) { scanf(&quot;%d&quot;, &amp;a[j]); } int sgn = 0;//用来标记符合条件的位置，标记型变量，必须初始化 for (int j = 5; j &lt;= n; j++) { for (int k = j - 4; k &lt;= j; k++) { b[j] += a[k];//注意此处有累加求和，所以b必须初始化！ } b[j] /= 5; } for (int j = 7; j &lt;= n; j++) { if (b[j] &lt; b[j - 1] &amp;&amp; b[j - 1] &lt; b[j - 2] &amp;&amp; a[j] &gt; a[1]) { sgn = j;//符合条件，跳出 break; } } printf(&quot;%d\\n&quot;, sgn); } return 0; } 示例代码 - 2 #include &lt;stdio.h&gt; int main() { int t; scanf(&quot;%d&quot;, &amp;t); for (int i = 1; i &lt;= t; i++) { int a[109];//存储原始数据 double b[109];//存储五日均值 int n; scanf(&quot;%d&quot;, &amp;n); for (int j = 1; j &lt;= n; j++) { scanf(&quot;%d&quot;, &amp;a[j]); } int sgn = 0;//用来标记符合条件的位置，标记型变量，必须初始化 for (int j = 5; j &lt;= n; j++) { b[j] = a[j] + a[j - 1] + a[j - 2] + a[j - 3] + a[j - 4]; //此处没有累加求和，只有赋值，不初始化也可以 b[j] /= 5; } for (int j = 7; j &lt;= n; j++) { if (b[j] &lt; b[j - 1] &amp;&amp; b[j - 1] &lt; b[j - 2] &amp;&amp; a[j] &gt; a[1]) { sgn = j;//符合条件，跳出 break; } } printf(&quot;%d\\n&quot;, sgn); } return 0; } G 夜色名为温柔 难度 考点 3 结构化编程 题目分析 本题可以用嵌套循环去遍历二维方格中的每一个点，根据坐标位置判断需要输出何种字符。 我们可以先储存所有星星的位置，对于每一个坐标，再嵌套一个循环判断该坐标是否属于某颗星星的拖尾或者本体。 示例代码 #include &lt;stdio.h&gt; int main() { // 输入与数据储存 int x0, y0, n; scanf(&quot;%d %d&quot;, &amp;x0, &amp;y0); scanf(&quot;%d&quot;, &amp;n); int x[101], y[101]; // 储存星星坐标 for (int i = 0; i &lt; n; i++) { scanf(&quot;%d %d&quot;, &amp;x[i], &amp;y[i]); } for (int i = y0; i &gt;= 0; i--) { for (int j = 0; j &lt;= x0; j++) { if (i == 0) { // 如果为地面，直接输出'_' printf(&quot;_&quot;); continue; } int sign = 0; // 记录是否是星星或拖尾 for (int k = 0; k &lt; n; k++) { if (x[k] == j &amp;&amp; y[k] &lt; i) { // 星星 sign = 1; printf(&quot;|&quot;); }else if (x[k] == j &amp;&amp; y[k] == i){ // 拖尾 sign = 1; printf(&quot;*&quot;); } } if (!sign) { // 不为星星或拖尾时，才需要输出空格 printf(&quot; &quot;); } } printf(&quot;\\n&quot;); } return 0; } H BaconToast吃旋转小火锅 难度 考点 4 循环、模拟 题目分析 1.难点一：环形数组？ 这道题目的食物种类数据是环形排列的，你可能会想到使用数组储存这些信息，但如何把数组的头和尾“连接”在一起呢？马上能想到两种思路： 每次当前数组下标达到了 nnn （即数组的最大下标加1）就将这个下标改成 000； 数组下标对 nnn 取模，得到的值位于 [0,n−1][0,n-1][0,n−1] （ i=ni=ni=n 映射到 i=0i=0i=0，i=n+1i=n+1i=n+1 映射到 i=1i=1i=1...）。 2.难点二：删除元素？ 如何表示这盘食物被吃掉了？对数组的某个元素进行“删除”似乎有些难以做到，这里有两种思路： 将数组元素变成 000 和 111 之外的其他值，例如 −1-1−1； 另外用一个初始值全为 000 的数组 vis[1005]表示是否这盘食物被访问（吃掉），吃掉后元素置为 111 。 示例代码 #include &lt;stdio.h&gt; int main() { int n, m; int a[1005], vis[1005] = {0}; int sum = 0; scanf(&quot;%d%d&quot;, &amp;n, &amp;m); int num = n; for (int i = 0; i &lt; n; i++) { scanf(&quot;%d&quot;, &amp;a[i]); } int p = 0; while (num &gt; 1) { if (vis[p] == 0) { sum += (a[p] == 0) ? 1 : 3; vis[p] = 1; num--; } for (int i = 0; i &lt;= m; i) { p++; if (p &gt;= n) p %= n; if (vis[p] == 0) i++; } } printf(&quot;%d %d&quot;, sum, a[p]); return 0; } 这个几个人围成一圈依次报数并淘汰出局的经典问题称为约瑟夫环，除了上述的模拟过程，这个问题还有使用链表数据结构以及时间更加优化的递归解法，大家感兴趣可以自己探索一下~ I 我要吃到最后一块糖果！(Hard Version) 难度 考点 5 数学，进制转换 题目分析 nnn 块糖果，每次拿 kkk 的幂次个，并且尽可能的多拿，如果把 nnn 转化为 kkk 进制数，那么就相当于每次只能让最高位减一，直到减到零为止，所以人们拿到糖果的总数就是 nnn 的 kkk 进制数各位数字之和，当然，这是 k≠1k\\ne 1k​=1 的情况，如果 k=1k=1k=1 ，总数就是 nnn 。 得到总共拿的次数之后，拿它除以 mmm 求余，余数就是刚好轮到的人的编号，那么他的上一个人就是拿到最后一块糖果的人，需要注意的是，直接让余数减 111 可能会出现 −1-1−1 ，这时，对应的答案是 (m−1)(m-1)(m−1) ，应当特殊判断。 示例代码 - 1 #include &lt;stdio.h&gt; int main(){ int m,k,t; scanf(&quot;%d%d%d&quot;,&amp;m,&amp;k,&amp;t); while(t--){ int n; scanf(&quot;%d&quot;,&amp;n); int ans=0; if(k==1){ ans=n; } else{ while(n){ ans+=n%k; n/=k; } } ans=ans%m-1; if(ans==-1){ ans+=m; } printf(&quot;%d\\n&quot;,ans); } return 0; } 由于本题每个测试点的 mmm 和 kkk 是固定的，所以也可以先拿一个数组来储存答案。 示例代码 - 2 #include &lt;stdio.h&gt; #define N 1000000 int f[N+5]; int main(){ int m,k,t; scanf(&quot;%d%d%d&quot;,&amp;m,&amp;k,&amp;t); for(int i=1;i&lt;=N;i++){ if(k==1){ f[i]=i; } else{ int n=i; while(n){ f[i]+=n%k; n/=k; } } } while(t--){ int n; scanf(&quot;%d&quot;,&amp;n); int ans=f[n]%m-1; if(ans==-1){ ans+=m; } printf(&quot;%d\\n&quot;,ans); } return 0; } 扩展 上面代码的时间复杂度分别为 O(∑i=1tlog⁡kni)O\\left(\\sum_{i=1}^t \\log_kn_i\\right)O(∑i=1t​logk​ni​) 和 O(Nlog⁡kN+t)O(N\\log_k N+t)O(Nlogk​N+t) ，都是 O(nlog⁡n)O(n\\log n)O(nlogn) 级别的复杂度，本题还有时间复杂度为 O(N+t)O(N+t)O(N+t) 的做法。 重点在于当 k≠1k\\neq 1k​=1 时如何快速求出 nnn 的 kkk 进制数中各位数字之和。 设 xxx 的 kkk 进制数中各位数字之和为 f(x)f(x)f(x). 考虑一般情况：如果 xxx 的 kkk 进制数加一之后没有产生进位，那么有递推关系 f(x+1)=f(x)+1f(x+1)=f(x)+1f(x+1)=f(x)+1 , 那么如何判断是否产生进位呢？为了方便，我们将该式变为 f(x)=f(x−1)+1f(x)=f(x-1)+1f(x)=f(x−1)+1 注意到，如果 (x−1)(x-1)(x−1) 增加 111 之后产生了进位，那么 xxx 的 kkk 进制表示中的最后一位是 000 ，也就是说 xxx 被 kkk 整除。所以得出结论：当 xxx 除以 kkk 的余数不为 000 时，有 f(x)=f(x−1)+1f(x)=f(x-1)+1f(x)=f(x−1)+1。 然后再考虑 xxx 可以被 kkk 整除的情况： 因为此时 xxx 的 kkk 进制表示中最后一位是 000 ，所以它各位数字之和就等于把最后一位去掉后剩下的数的各位数字之和，例如对于 x=39,k=3x=39,k=3x=39,k=3 来说，xxx 的 333 进制表示为 (410)3(410)_3(410)3​ ，它与 (41)3(41)_3(41)3​ 的各位数字之和相等，注意到 (41)3=(13)10(41)_3=(13)_{10}(41)3​=(13)10​ ，得到 131313 只需将 xxx 除以 kkk。 可以根据下面的式子得出： x=ankn+an−1kn−1+...+a1k+0⋅k0xk=ankn−1+an−1kn−2...+a2k+a1k0x=a_nk^n+a_{n-1}k^{n-1}+...+a_1k+0\\cdot k^0\\\\ \\frac{x}{k}=a_nk^{n-1}+a_{n-1}k^{n-2}...+a_2k+a_1k^0 x=an​kn+an−1​kn−1+...+a1​k+0⋅k0kx​=an​kn−1+an−1​kn−2...+a2​k+a1​k0 此时可以得到： f(x)=f(xk)=∑i=1naif(x)=f\\left(\\frac{x}{k}\\right)=\\sum_{i=1}^na_i f(x)=f(kx​)=i=1∑n​ai​ 综上所述，可以得到 f(x)f(x)f(x) 的递推关系式： f(x)={f(x−1)+1, k∤nf(xk), k∣nf(x)= \\begin{cases} f(x-1)+1&amp;,~k\\nmid n\\\\ f\\left(\\frac{x}{k}\\right)&amp;,~k\\mid n \\end{cases} f(x)={f(x−1)+1f(kx​)​, k∤n, k∣n​ Tips: (k∣n)(k\\mid n)(k∣n) 指 kkk 能整除 nnn，即 nnn 能被 kkk 整除，nnn 除以 kkk 的余数是 000 。(k∤n)(k\\nmid n)(k∤n) 指 kkk 不能整除 nnn 。 f(x)f(x)f(x) 代表 k≠1k\\ne 1k​=1 时 xxx 的 kkk 进制数中各位数字之和，如果想让它代表人们拿糖果的总次数，还需要定义 k=1k=1k=1 时 f(x)=xf(x)=xf(x)=x ，我们发现，此时仍然满足递推关系 f(x)=f(x−1)+1f(x)=f(x-1)+1f(x)=f(x−1)+1 ，所以，我们可以把 f(x)f(x)f(x) 的含义变为拿糖果的总次数，并且符合如下递推关系式： f(x)={f(x−1)+1, k=1,或k∤nf(xk), k≠1,且k∣nf(x)= \\begin{cases} f(x-1)+1&amp;,~k=1,或k\\nmid n\\\\ f\\left(\\frac{x}{k}\\right)&amp;,~k\\ne1,且k\\mid n \\end{cases} f(x)={f(x−1)+1f(kx​)​, k=1,或k∤n, k​=1,且k∣n​ 在程序中，用数组 f[] 来表示函数 fff ，先将 f[] 初始化，然后在每次询问中输出答案即可。 示例代码 - 3 #include &lt;stdio.h&gt; #define N 1000000 int f[N+5]; int main(){ int m,k; scanf(&quot;%d%d&quot;,&amp;m,&amp;k); for(int i=1;i&lt;=N;i++){ if(k==1||i%k!=0){ f[i]=f[i-1]+1; } else{ f[i]=f[i/k]; } } int t; scanf(&quot;%d&quot;,&amp;t); while(t--){ int n; scanf(&quot;%d&quot;,&amp;n); int ans=f[n]%m-1; if(ans==-1){ ans=ans+m; } printf(&quot;%d\\n&quot;,ans); } return 0; } J 优香的掷骰赛跑 3rd 难度 考点 6 判断、数论、区间的交并运算 题目分析 不妨设优香投掷了 xxx 次骰子，我们可以列举一下优香投掷 0,1,2,3,…,x0,1,2,3,\\dots,x0,1,2,3,…,x 次骰子会出现的点数： 投掷次数xxx 获得点数aaa 000 000 111 1,3,41,3,41,3,4 222 2,4,5,6,7,82,4,5,6,7,82,4,5,6,7,8 333 3,5,6,7,8,9,10,11,123,5,6,7,8,9,10,11,123,5,6,7,8,9,10,11,12 …\\dots… …\\dots… xxx x,x+2,x+3,…,4xx,x+2,x+3,\\dots,4xx,x+2,x+3,…,4x 推导可知（由附录命题1）， {a=x, x≥0x+2≤a≤4x, x≥1\\begin{cases} a=x,\\ \\ x\\ge 0\\\\ x+2\\le a \\le 4x ,\\ \\ x\\ge 1 \\end{cases} {a=x, x≥0x+2≤a≤4x, x≥1​ 反解 xxx 我们即可得到一个关于 xxx 的解集 （N\\text{N}N 表示自然数集，下同）： A={x=a, a≥0a4≤x≤a−2, a≥3 ,x∈NA=\\begin{cases} x=a,\\ a \\ge 0\\\\ \\dfrac{a}{4}\\le x \\le a-2,\\ \\ a\\ge 3 \\end{cases} \\ \\ \\ \\ \\ \\ ,x\\in \\text{N} A={x=a, a≥04a​≤x≤a−2, a≥3​ ,x∈N 同理，我们不妨设白子投掷了 yyy 次骰子，我们可以列举一下白子投掷 0,1,2,3,4,…,y0,1,2,3,4,\\dots,y0,1,2,3,4,…,y 次骰子会出现的点数： 投掷次数yyy 获得点数bbb 000 000 111 1,5,61,5,61,5,6 222 2,6,7,10,11,122,6,7,10,11,122,6,7,10,11,12 333 3,7,8,11,12,13,15,16,17,183,7,8,11,12,13,15,16,17,183,7,8,11,12,13,15,16,17,18 444 4,8,9,12,13,14,16,17,18,19,20,21,22,23,244,8,9,12,13,14,16,17,18,19,20,21,22,23,244,8,9,12,13,14,16,17,18,19,20,21,22,23,24 …\\dots… …\\dots… yyy y,y+4,y+5,y+8,y+9,y+10,y+12,y+13,…,6yy,y+4,y+5,y+8,y+9,y+10,y+12,y+13,\\dots,6yy,y+4,y+5,y+8,y+9,y+10,y+12,y+13,…,6y 推导可知（由附录命题2）， {y=b, y≥0y+4≤b≤y+5, y≥1y+8≤b≤y+10, y≥2y+12≤b≤6y, y≥3\\begin{cases} y=b,\\ \\ y\\ge 0 \\\\ y+4\\le b\\le y+5,\\ \\ y\\ge 1\\\\ y+8\\le b\\le y+10,\\ \\ y\\ge 2\\\\ y+12\\le b\\le 6y,\\ \\ y\\ge 3 \\end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧​y=b, y≥0y+4≤b≤y+5, y≥1y+8≤b≤y+10, y≥2y+12≤b≤6y, y≥3​ 我们又知道 y=n−xy=n-xy=n−x ，反解 xxx 我们即可得到一个关于 xxx 的解集 ： B={x=n−b, n−x≥0n+4−b≤x≤n+5−b, n−x≥1n+8−b≤x≤n+10−b, n−x≥2n+12−b≤x≤6n−b6, n−x≥3 ,x∈NB=\\begin{cases} x=n-b,\\ \\ n-x\\ge 0\\\\ n+4-b\\le x\\le n+5-b,\\ \\ n-x\\ge 1\\\\ n+8-b\\le x\\le n+10-b,\\ \\ n-x\\ge 2\\\\ n+12-b\\le x\\le \\dfrac{6n-b}{6},\\ \\ n-x\\ge 3\\\\ \\end{cases} \\ \\ \\ \\ \\ \\ ,x\\in \\text{N} B=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧​x=n−b, n−x≥0n+4−b≤x≤n+5−b, n−x≥1n+8−b≤x≤n+10−b, n−x≥2n+12−b≤x≤66n−b​, n−x≥3​ ,x∈N 我们可以断定，x∈A∩Bx \\in A \\cap Bx∈A∩B 就是符合条件的解。只要我们能找到一个 xxx 在这个交集中，我们就可以断言 n,a,bn,a,bn,a,b 符合题意。我们可以发现 AAA 是两段区间的并， BBB 是四段区间的并。因此我们只需要让 A,BA,BA,B​ 每段区间之间互相计算其交集，穷举八种情况即可判断有无符合条件。每一段区间的存在性都有其先决条件，因此需要判断每段区间成立的先决条件是否成立。 区间 a≤x≤ba\\le x\\le ba≤x≤b 与区间 c≤x≤dc\\le x\\le dc≤x≤d 没有交集，一定有 b&lt;cb&lt;cb&lt;c 或者 d&lt;ad&lt;ad&lt;a ，否则就有交集。可以用这个办法判断两个区间是否有交集。（也可利用其他方法） 注意，a4,6n−b6\\dfrac{a}{4},\\dfrac{6n-b}{6}4a​,66n−b​ 不一定是整数，因此我们需要对 a4\\dfrac{a}{4}4a​ 向上取整，6n−b6\\dfrac{6n-b}{6}66n−b​ 向下取整。C语言中向上向下取整的方法如下： //a b均为整型变量 c=a/b; //a b均为正数的时候向下取整 c=(a+b-1)/b; //a b均为正数的时候向上取整 c=a/b; //a为负数，b为正数的时候向上取整 c=(a-b+1)/b; //a为负数，b为正数的时候向下取整 //更多的可以尝试自己推导 示例代码 #include &lt;stdio.h&gt; #define max(x, y) (((x) &gt; (y)) ? (x) : (y)) #define min(x, y) (((x) &lt; (y)) ? (x) : (y)) //上面的是可以用来比较大小的两个宏，大多数时候可以适用，但不保证不出错 int main() { long long n, a, b; while (scanf(&quot;%lld%lld%lld&quot;, &amp;n, &amp;a, &amp;b) != EOF) { long long sgn = 0; long long hi; if (6 * n - b &gt;= 0) { hi = (6 * n - b) / 6; } else { hi = (6 * n - b - 5) / 6; }//对6*n-b向下取整 if (a == n - b) { sgn = 1; } if (n + 4 - b &lt;= a &amp;&amp; a &lt;= n + 5 - b &amp;&amp; n - a &gt;= 1) { sgn = 1; } if (n + 8 - b &lt;= a &amp;&amp; a &lt;= n + 10 - b &amp;&amp; n - a &gt;= 2) { sgn = 1; } if (n + 12 - b &lt;= a &amp;&amp; a &lt;= hi &amp;&amp; n - a &gt;= 3) { sgn = 1; } if (a &gt;= 3) { long long minimum; if ((a + 3) / 4 &lt;= n - b &amp;&amp; n - b &lt;= a - 2) { sgn = 1; } minimum = max(((a + 3) / 4), (n + 4 - b)); if (!(a - 2 &lt; n + 4 - b || n + 5 - b &lt; (a + 3) / 4) &amp;&amp; minimum &lt;= n - 1) { sgn = 1; } minimum = max(((a + 3) / 4), (n + 8 - b)); if (!(a - 2 &lt; n + 8 - b || n + 10 - b &lt; (a + 3) / 4) &amp;&amp; minimum &lt;= n - 2) { sgn = 1; } minimum = max(((a + 3) / 4), (n + 12 - b)); if (!(a - 2 &lt; n + 12 - b || hi &lt; (a + 3) / 4) &amp;&amp; minimum &lt;= n - 3) { sgn = 1; } } if (sgn == 1) { printf(&quot;True\\n&quot;); } else { printf(&quot;False\\n&quot;); } } return 0; } 附录 命题1：x≥1x\\ge 1x≥1 时，xxx 个为 1,3,41,3,41,3,4 的数字，求和的结果一定是 x,x+2,…,4xx,x+2,\\dots,4xx,x+2,…,4x 之一。 证明1： 易知 xxx 个为 1,3,41,3,41,3,4 的数字，求和的结果一定在 xxx 与 4x4x4x 之间，且x=1x=1x=1 时对于命题1显然成立。 假设 x=k, k≥1x=k,\\ \\ k\\ge 1x=k, k≥1 时命题1成立。当 x=k+1x=k+1x=k+1 时，我们令多出的数字为 111 ，则求和的结果一定是 k+1,k+3,…,4k+1k+1,k+3,\\dots,4k+1k+1,k+3,…,4k+1 之一。 同理，我们令多出的数字为 333 ，则求和的结果一定是 k+3,k+5,…,4k+3k+3,k+5,\\dots,4k+3k+3,k+5,…,4k+3 之一；我们令多出的数字为 444 ，则求和的结果一定是 k+4,k+6,…,4k+4k+4,k+6,\\dots,4k+4k+4,k+6,…,4k+4 之一； 上述三种情况取并即可得出：当 x=k+1x=k+1x=k+1 时，求和的结果一定是 k+1,k+3,…,4(k+1)k+1,k+3,\\dots,4(k+1)k+1,k+3,…,4(k+1) 之一，归纳假设成立。 命题2：y≥3y\\ge 3y≥3 时，yyy 个为 1,5,61,5,61,5,6 的数字，求和的结果一定是 y,y+4,y+5,y+8,y+9,y+10,y+12,y+13,…,6yy,y+4,y+5,y+8,y+9,y+10,y+12,y+13,\\dots,6yy,y+4,y+5,y+8,y+9,y+10,y+12,y+13,…,6y 之一。 证明2： 易知 yyy 个为 1,5,61,5,61,5,6 的数字，求和的结果一定在 yyy 与 6y6y6y 之间，且y=3y=3y=3 时对于命题2显然成立。 假设 y=k, k≥3y=k,\\ \\ k\\ge 3y=k, k≥3 时命题1成立。当 y=k+1y=k+1y=k+1 时，我们令多出的数字为 111 ，则求和的结果一定是 k+1,k+5,k+6,k+9,k+10,k+11,k+13,k+14,…,6k+1k+1,k+5,k+6,k+9,k+10,k+11,k+13,k+14,\\dots,6k+1k+1,k+5,k+6,k+9,k+10,k+11,k+13,k+14,…,6k+1 之一。 同理我们令多出的数字为 555 ，则求和的结果一定是 k+5,k+9,k+10,k+13,k+14,k+15,k+17,k+18,…,6k+5k+5,k+9,k+10,k+13,k+14,k+15,k+17,k+18,\\dots,6k+5k+5,k+9,k+10,k+13,k+14,k+15,k+17,k+18,…,6k+5 之一；我们令多出的数字为 666 ，则求和的结果一定是 k+6,k+10,k+11,k+14,k+15,k+16,k+18,k+19,…,6k+6k+6,k+10,k+11,k+14,k+15,k+16,k+18,k+19,\\dots,6k+6k+6,k+10,k+11,k+14,k+15,k+16,k+18,k+19,…,6k+6 之一； 上述三种情况取并即可得出：当 y=k+1y=k+1y=k+1 时，求和的结果一定是 (k+1),(k+1)+4,(k+1)+5,(k+1)+8,(k+1)+9,(k+1)+10,(k+1)+12,(k+1)+13,…,6(k+1)(k+1),(k+1)+4,(k+1)+5,(k+1)+8,(k+1)+9,(k+1)+10,(k+1)+12,(k+1)+13,\\dots,6(k+1)(k+1),(k+1)+4,(k+1)+5,(k+1)+8,(k+1)+9,(k+1)+10,(k+1)+12,(k+1)+13,…,6(k+1) 之一，归纳假设成立。 ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C4 - Solution-24航c","link":"https://github.pansis.site/post/C4 - Solution-24航c/","stats":{"text":"30 min read","time":1756000,"words":6057,"minutes":30},"dateFormat":"2024-10-18"},{"content":"A Firefly小姐的另一道水题 浮点数判断相等 eps=0.000001 fabs(a-b)&lt;eps 题解 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main() { double a,b,c; while ((scanf(&quot;%lf%lf%lf&quot;,&amp;a,&amp;b,&amp;c))!=EOF) { if (fabs(a+b-c)&lt;0.000001) printf(&quot;Yes\\n&quot;); else printf(&quot;No\\n&quot;); } } B 算个乘方（水题速来） 此题禁用pow函数 pow函数的输出类型是double 当pow函数输出值过大时，误差会超过1。因此整数范围内禁用pow 整数范围的乘方 long long res=1; for (int i = 0; i &lt; k; ++i) { res*=n; } printf(&quot;%lld\\n&quot;,res); 特别的，2的n次方=1&lt;&lt;n （注意n的范围，当n大于等于32时1改成1ll） 题解 #include &lt;stdio.h&gt; int main() { long long n,k; while (scanf(&quot;%lld%lld&quot;,&amp;n,&amp;k)!=EOF) { long long res=1; for (int i = 0; i &lt; k; ++i) { res*=n; } printf(&quot;%lld\\n&quot;,res); } } C 山 字符画题搞清楚每一行输出字符数量与行数的关系即可 #include &lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;,&amp;n); for (int i = 1; i &lt;= n; ++i) { for (int j = 0; j &lt; n-i; ++j) { printf(&quot; &quot;); } for (int j = 0; j &lt; 2*i-1; ++j) { printf(&quot;A&quot;); } printf(&quot;\\n&quot;); } } D Deadline 的艺术（复活版） %2d %3d scanf(&quot;%2d%2d%2d%2d&quot;,&amp;a,&amp;b,&amp;c,&amp;d); //输入：20020829 //则a=20 b=2 c=8 d=29 本题易错点 20000130 题解 #include &lt;stdio.h&gt; int main() { int c,y,m,d; int num; while (scanf(&quot;%2d%2d%2d%2d&quot;,&amp;c,&amp;y,&amp;m,&amp;d)!=EOF) { if (m==1||m==2) { m+=12; y--; if (y==-1) { y=99; c--; } } int dd=c/4-2*c+y+y/4+(13*(m+1)/5)+d-1; dd=(dd%7+7)%7; switch (dd) { case 0: printf(&quot;Sunday&quot;); break; case 1: printf(&quot;Monday&quot;); break; case 2: printf(&quot;Tuesday&quot;); break; case 3: printf(&quot;Wednesday&quot;); break; case 4: printf(&quot;Thursday&quot;); break; case 5: printf(&quot;Friday&quot;); break; case 6: printf(&quot;Saturday&quot;); break; } printf(&quot;\\n&quot;); } } while (scanf(&quot;%d&quot;, &amp;a) == 1)//另一种输入 { c = a / 1000000; y = a / 10000 % 100; m = a / 100 % 100; d = a % 100; E 开幕式 题解 #include &lt;stdio.h&gt; int main() { int n; int a; int b; int num=0; scanf(&quot;%d%d&quot;,&amp;n,&amp;a); num+=a; for (int i = 0; i &lt; n-1; ++i) { scanf(&quot;%d&quot;,&amp;b); num+=b; while(b!=0) { int temp=a%b; //因为a和b的值都要更新，所以用temp来保存！ a=b; b=temp; } } printf(&quot;%d&quot;,num/a); } F Ma5线 题解 #include &lt;stdio.h&gt; int main() { int t; scanf(&quot;%d&quot;,&amp;t); for (int i = 0; i &lt; t; ++i) { int n; scanf(&quot;%d&quot;,&amp;n); int a[105]; for (int j = 1; j &lt;= n; ++j) { scanf(&quot;%d&quot;,&amp;a[j]); } int sum[105]={0}; int flag=0; for (int j = 5; j &lt;=n ; ++j) { sum[j]=a[j]+a[j-1]+a[j-2]+a[j-3]+a[j-4]; if (sum[j]&lt;sum[j-1]&amp;&amp;sum[j-1]&lt;sum[j-2]&amp;&amp;a[j]&gt;a[1]) { printf(&quot;%d\\n&quot;,j); flag=1; break; } } if (flag==0) printf(&quot;0\\n&quot;); } } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C4讲解-24航c","link":"https://github.pansis.site/post/C4讲解-24航c/","stats":{"text":"4 min read","time":236000,"words":704,"minutes":4},"dateFormat":"2024-10-18"},{"content":"A Firefly小姐的水题 题解 #include&lt;stdio.h&gt; int main() { unsigned int a,b; scanf(&quot;%u%u&quot;,&amp;a,&amp;b); printf(&quot;%u&quot;,a&amp;b); } 位运算符 &amp;按位与 |按位或 ^按位异或 ~取反 &lt;&lt;左移 &gt;&gt;右移 按位与 A B A&amp;B 1 1 1 0 1 0 1 0 0 0 0 0 按位或 A B A|B 1 1 1 0 1 1 1 0 1 0 0 0 按位异或 A B A^B 1 1 0 0 1 1 1 0 1 0 0 0 按位取反~ 1变0,0变1 B 你是谁 对某一位置1：按位或 n|(1&lt;&lt;x) n=110100 10110 00101 00001 对某一位置0：按位与 n&amp;(~(1&lt;&lt;x)) 判定某一位的值：(n&gt;&gt;x)&amp;1 1、左移右移时注意移位范围 1&lt;&lt;35=0 错误 1ll&lt;&lt;35=2352^{35}235 2、表示某个变量的补码 计算机存储的值均以补码的方式存储，提取补码时不需要对原值进行任何转换。 正数的补码就是其本身的二进制值。 负数的补码是本身二进制值取反加1。 #include &lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;,&amp;n); for (int i = 31; i &gt;=0; --i) { printf(&quot;%d&quot;,(n&gt;&gt;i)&amp;1); } } C Firefly小姐的位运算大练习—入门版 题解 #include&lt;stdio.h&gt; int main() { unsigned int n; int x; int t; scanf(&quot;%d&quot;,&amp;t); for (int i = 0; i &lt; t; ++i) { scanf(&quot;%u%d&quot;,&amp;n,&amp;x); printf(&quot;%d\\n&quot;,(n&gt;&gt;x)&amp;1); } } C Firefly小姐的位运算大练习—入门版 题解 #include&lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;,&amp;n); for (int i = 0; i &lt; n; ++i) { unsigned int a,l; int op; scanf(&quot;%d&quot;,&amp;op); if (op==1) { scanf(&quot;%u&quot;,&amp;a); printf(&quot;%u\\n&quot;,~a); } else if (op==2) { scanf(&quot;%u%u&quot;,&amp;a,&amp;l); printf(&quot;%u\\n&quot;,a&lt;&lt;l); } else { scanf(&quot;%u%u&quot;,&amp;a,&amp;l); printf(&quot;%u\\n&quot;,a&gt;&gt;l); } } } D cancanneed真值表 题解 #include&lt;stdio.h&gt; int main() { unsigned int a,b; while (scanf(&quot;%u%u&quot;,&amp;a,&amp;b)!=EOF) { printf(&quot;%u\\n&quot;,(~a)&amp;b); } } E 小牛与内存压缩 题解 #include&lt;stdio.h&gt; int main() { unsigned int a,b; while (scanf(&quot;%u%u&quot;,&amp;a,&amp;b)!=EOF) { printf(&quot;%u\\n&quot;,(~a)&amp;b); } } F 一位全加器先生 #include&lt;stdio.h&gt; int main() { int a,b,c; scanf(&quot;%d%d%d&quot;,&amp;a,&amp;b,&amp;c); int fa=~a,fb=~b,fc=~c; int s=((fa&amp;fb&amp;c)|(fa&amp;b&amp;fc)|(a&amp;fb&amp;fc)|(a&amp;b&amp;c))&amp;1; int cc=((a&amp;c)|(b&amp;c)|(a&amp;b))&amp;1; printf(&quot;%d %d&quot;,s,cc); } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C3讲解-24航C","link":"https://github.pansis.site/post/C3讲解-24航C/","stats":{"text":"4 min read","time":205000,"words":642,"minutes":4},"dateFormat":"2024-10-10"},{"content":"A Firefly小姐的水题 难度 考点 1 ASCII码，多组数据输入 题目分析 与运算的运算符是 &amp;，注意题目要求使用 unsigned int 类型进行输入输出。 示例代码 #include &lt;stdio.h&gt; int main(){ unsigned int a, b; scanf(&quot;%u %u&quot;, &amp;a, &amp;b); printf(&quot;%u&quot;, a &amp; b); return 0; } Author:SiSi{\\LARGE {\\color{yellowgreen} \\mathscr{Author:SiSi}}}Author:SiSi B 你是谁 难度 考点 1 位运算 题目分析 由于规定最低位是第 000 位，所以只需对 nnn 右移 xxx 位，然后取最低位即可。 可以用 n &amp; 1 来取最低位。 示例代码 #include &lt;stdio.h&gt; int main(){ int t; scanf(&quot;%d&quot;,&amp;t); while(t--){ unsigned int n; int x; scanf(&quot;%u%d&quot;,&amp;n,&amp;x); n&gt;&gt;=x;// n = n &gt;&gt; x; printf(&quot;%d\\n&quot;,n&amp;1); } return 0; } C Firefly小姐的位运算大练习—入门版 难度 考点 1 多组数据输入 题目分析 在题目给定的模版中填充计算的部分即可。 示例代码 #include &lt;stdio.h&gt; int main(){ int n; scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { int op; scanf(&quot;%d&quot;, &amp;op); if (op == 1) { // 取反 unsigned int a; scanf(&quot;%u&quot;, &amp;a); printf(&quot;%u\\n&quot;, ~a); }else if (op == 2) { // 左移 unsigned int a; int l; scanf(&quot;%u %d&quot;, &amp;a, &amp;l); printf(&quot;%u\\n&quot;, a &lt;&lt; l); }else if (op == 3) { // 右移 unsigned int a; int l; scanf(&quot;%u %d&quot;, &amp;a, &amp;l); printf(&quot;%u\\n&quot;, a &gt;&gt; l); } } return 0; } Author:SiSi{\\LARGE {\\color{yellowgreen} \\mathscr{Author:SiSi}}}Author:SiSi D cancanneed真值表 难度 考点 2 位运算 题目分析 题中的真值表给出了一个不同于基本位运算的运算逻辑，我们猜想经过多次基本位运算后可以得到这个结果。对 aaa 按位取反，记为 a‾\\overline aa ，我们发现 a⊙ba\\odot ba⊙b 正好是 a‾\\overline aa 与 bbb 按位与运算后的结果。 aaa a‾\\overline aa bbb (a⊙b)(a\\odot b)(a⊙b) 0 1 0 0 0 1 1 1 1 0 0 0 1 0 1 0 示例代码 #include &lt;stdio.h&gt; int main() { unsigned int a, b; while(scanf(&quot;%u%u&quot;, &amp;a, &amp;b) != EOF) { printf(&quot;%u\\n&quot;, (~a) &amp; b); } return 0; } E 小牛与内存压缩 难度 考点 2 位运算（左移、或） 题目分析 题目要求我们将两个非负整数aaa和bbb压缩到一个 unsigned int 类型的数ansansans中，具体的方式是将 aaa 存储在 ans 的高 161616 位，bbb存储在低 161616 位。这样，通过位运算可以将两个小于2162^{16}216的整数合并为一个 unsigned int 类型的值。具体实现思路如下： 将aaa左移 16 位，使其移动到 ans 的高 161616 位，即 a &lt;&lt; 16。 此时ans的低161616位全是000。 通过按位或运算符 |，将bbb放置在 ans 的低 161616 位，即ans | b。 具体代码如下： 示例代码 #include&lt;stdio.h&gt; int main() { int n; unsigned int a, b, ans; // 输入组数 n scanf(&quot;%d&quot;, &amp;n); // 处理每组数据 while (n--) { // 输入 a 和 b scanf(&quot;%u%u&quot;, &amp;a, &amp;b); // 将 a 左移 16 位，并与 b 做按位或运算 ans = (a &lt;&lt; 16) | b; // 输出压缩后的结果 printf(&quot;%u\\n&quot;, ans); } return 0; } F 一位全加器先生 难度 考点 2 真值表、位运算 题目分析 本题模拟位运算实现加法的过程，我们可以根据真值表推断出来以下结论，直接输出即可： si = ai ^ bi ^ cin//多个异或可以判断出来数字1的数量的奇偶 cout = (ai &amp; bi) | (ai &amp; cin) | (cin &amp; bi)//三个数中至少要有两个1 示例代码 - 1 #include&lt;stdio.h&gt; int main() { int ai, bi, cin; scanf(&quot;%d%d%d&quot;, &amp;ai, &amp;bi, &amp;cin); printf(&quot;%d &quot;, ai ^ bi ^ cin); printf(&quot;%d&quot;, (ai &amp; bi) | (ai &amp; cin) | (cin &amp; bi)); return 0; } 示例代码 - 2 没能观察出来上述规律怎么办？其实根据Hint，本题完全可以通过写 888 个 if 的方式解决： #include&lt;stdio.h&gt; int main() { int ai, bi, cin; scanf(&quot;%d%d%d&quot;, &amp;ai, &amp;bi, &amp;cin); if (ai == 0 &amp;&amp; bi == 0 &amp;&amp; cin == 0) { printf(&quot;0 0&quot;); } if (ai == 0 &amp;&amp; bi == 0 &amp;&amp; cin == 1) { printf(&quot;1 0&quot;); } if (ai == 0 &amp;&amp; bi == 1 &amp;&amp; cin == 0) { printf(&quot;1 0&quot;); } if (ai == 1 &amp;&amp; bi == 0 &amp;&amp; cin == 0) { printf(&quot;1 0&quot;); } if (ai == 0 &amp;&amp; bi == 1 &amp;&amp; cin == 1) { printf(&quot;0 1&quot;); } if (ai == 1 &amp;&amp; bi == 0 &amp;&amp; cin == 1) { printf(&quot;0 1&quot;); } if (ai == 1 &amp;&amp; bi == 1 &amp;&amp; cin == 0) { printf(&quot;0 1&quot;); } if (ai == 1 &amp;&amp; bi == 1 &amp;&amp; cin == 1) { printf(&quot;1 1&quot;); } return 0; } 扩展阅读 我们在C语言中实现加法，只需要写 a + b 这样的语句即可。但是，实际上的计算机是没有原生的加法功能的，只能通过位运算来实现加法。示例代码 - 1 详细的写出了一位数加法在计算机底层的实现逻辑。你也可以继续研究，探索用位运算实现更多位数的加法的全过程。 G 流行计数 难度 考点 3 位运算 题目分析 1 要统计两个二进制数在相同位置上具有不同数字的位置的个数，我们可以利用循环，依次取出各个位上的数进行比较，如果不同则计数 +1。 如何取出某一位上的数呢？利用位运算 (x &gt;&gt; i) &amp; 1，我们可以实现这一目的： x &gt;&gt; i：将 x 以二进制的形式右移 i 位。注意 x 需为无符号数（要么声明为 unsigned 类型，要么保证其为正数，本题中数据保证为正数），有符号数的右移行为是未定义的。 有符号数的最高位为符号位，该位为 1 时表示负数。对负数进行右移，是否要保留符号位？是否要连符号位一起右移？这些做法都有道理，所以该行为在标准中是未定义的，在不同机器上有不同实现。 (x &gt;&gt; i) &amp; 1：将 x 右移 i 位之后，原本第 i 位上的数现在变成第 0 位上的数字了。而数 1 在二进制下只有第 0 位上为 1，其余位均为 0。将 (x &gt;&gt; i) 和 1 进行按位与运算，便可以保留其现在第 0 位上的数字，其余位清零，也就实现了“取出第 i 位上的数字”这一操作。 示例代码 1 #include &lt;stdio.h&gt; int main() { int a, b; scanf(&quot;%d%d&quot;, &amp;a, &amp;b); int cnt = 0; for(int i = 0; i &lt; 32; i += 1) { // 遍历第 0 到第 31 位 int bit_a = (a &gt;&gt; i) &amp; 1; int bit_b = (b &gt;&gt; i) &amp; 1; if (bit_a != bit_b) { cnt += 1; } } printf(&quot;%d&quot;, cnt); return 0; } 题目分析 2 分析题目，要解决问题，首先需要将两个数的二进制进行对比，其次遍历每一位，若两数在该位上数字不同，则计数 +1。 可以发现，将两数进行异或计算，相当于对每一位，若两数在该位上数字不同，则置 1，否则置 0。这恰好可以用来解决我们这道题：将两数异或之后，得到的结果的二进制表示中 1 的个数就是两个数在相同位置上具有不同数字的位置的个数，只需要遍历即可。 示例代码 2 #include &lt;stdio.h&gt; int main() { int a, b; scanf(&quot;%d%d&quot;, &amp;a, &amp;b); int c = a ^ b; int cnt = 0; for(int i = 0; i &lt; 32; i += 1) { // 遍历第 0 到第 31 位 if(((c &gt;&gt; i) &amp; 1) == 1) { // 取第 i 位上的数，判断其是否为 1 cnt += 1; } } printf(&quot;%d&quot;, cnt); return 0; } 扩展 我们也可以使用一些技巧，加快遍历二进制数的速度： #include &lt;stdio.h&gt; int main() { int a, b; scanf(&quot;%d%d&quot;, &amp;a, &amp;b); int c = a ^ b; int cnt = 0; while(c &gt; 0) { c = c &amp; (c - 1); // * cnt += 1; } printf(&quot;%d&quot;, cnt); return 0; } 上述 * 式中，c-1 类似十进制减法中“借位”的思想，将 c 中为 1 的最低位转化为 0，其右所有位转为 1。 其次，将 c 与 c-1 进行按位与运算，就可以将 c 中为 1 的最低位及其右侧所有位转化为 0。 这样一次操作，我们可以将 c 中为 1 的一位转化为 0。所以，一次循环为一次操作，用 cnt 记录循环次数即可。 H 奇迹的二进制密码 难度 考点 3 位运算，循环 题目分析 由于unsigned int类型在二进制下共有32位，因此实际移动次数只需要将xxx取模即可。 简单的思路是每次用tmp暂存需要被移动的位，每次移动后将tmp填充至最高位或最低位即可。 或考虑不使用循环的解法，由于在移动前后各位次的顺序保持不变，因此可以在O(1)O(1)O(1)内完成求解，见示例代码2。 示例代码1 int main(){ int t; scanf(&quot;%d&quot;, &amp;t); unsigned int n, op; long x; while (t--) { scanf(&quot;%u %u %ld&quot;, &amp;n, &amp;op, &amp;x); int tmp; x %= 32; if (op == 1) { tmp = (n &gt;&gt; 31) &amp; 1; while (x--) { n &lt;&lt;= 1; n |= tmp; tmp = (n &gt;&gt; 31) &amp; 1; } } else{ //右移 tmp = n &amp; 1; while (x--) { n &gt;&gt;= 1; n |= (tmp &lt;&lt; 31); tmp = n &amp; 1; } } printf(&quot;%u\\n&quot;, n); } return 0; } 示例代码2 #include &lt;stdio.h&gt; int main() { int t; scanf(&quot;%d&quot;, &amp;t); while (t--) { unsigned int n, op, x; scanf(&quot;%u %u %u&quot;, &amp;n, &amp;op, &amp;x); if (op == 0){ x = (32 - x) % 32; } else{ x %= 32; } printf(&quot;%u\\n&quot;, ((n &gt;&gt; x) | (n &lt;&lt; (32 - x)))); } return 0; } I 请大家一定要做对这题啊！拜托了！ 难度 考点 4 任意进制转换 题目分析 题意：实现从 nnn 进制到 mmm 进制的转换。 子问题一：n 进制转换为十进制 以下是几个例子，请认真观察算式的组成，包括每一项指数的递减，以及每一位数字对应乘上的乘幂是多少。以我们熟悉的十进制数为参照可以帮助你更快地理解 nnn 进制到十进制的转换规律。 十进制数 514=5×102+1×101+4×100=514514 = 5 \\times 10 ^ 2 + 1 \\times 10 ^ 1 + 4 \\times 10 ^ 0 = 514 514=5×102+1×101+4×100=514 二进制数 (1110010)2=1×26+1×25+1×24+0×23+0×22+1×21+0×20=114(1110010)_2 = 1 \\times 2^6 + 1 \\times 2 ^ 5 + 1 \\times 2 ^ 4 + 0 \\times 2 ^ 3 + 0 \\times 2 ^ 2 + 1 \\times 2 ^ 1 + 0 \\times 2 ^ 0 = 114 (1110010)2​=1×26+1×25+1×24+0×23+0×22+1×21+0×20=114 八进制数 (1452)8=1×83+4×82+5×81+2×80=810(1452)_8 = 1 \\times 8 ^ 3 + 4 \\times 8 ^ 2 + 5 \\times 8 ^ 1 + 2 \\times 8 ^ 0 = 810 (1452)8​=1×83+4×82+5×81+2×80=810 十六进制数 (77F)16=7×162+7×161+15×160=1919(77F)_{16} = 7 \\times 16 ^ 2 + 7 \\times 16 ^ 1 + 15 \\times 16 ^ 0 = 1919 (77F)16​=7×162+7×161+15×160=1919 不难发现，nnn 进制下，第 iii 位数字对应的权重为 nin ^ ini （位数从 000 开始），这是 nnn 进制数“逢 nnn 进一”的性质所决定的。如果你仍然感到困惑，请思考在十进制下，为什么有 123=1×102+2×101+3×100123 = 1 \\times 10 ^2 + 2 \\times 10 ^1 + 3 \\times 10 ^ 0123=1×102+2×101+3×100 ，并将其推广到其他进制。 于是，对于最高位为第 ttt 位的 nnn 进制数 (atat−1at−2…a1a0‾)n(\\overline{a_ta_{t-1}a_{t-2}\\dots a_1a_0})_n(at​at−1​at−2​…a1​a0​​)n​ ，其十进制表示即为 ∑i=0tai×ni\\sum\\limits_{i = 0} ^ t a_i \\times n^i i=0∑t​ai​×ni 子问题二：十进制转换为 m 进制 请思考这样一个问题：对于一个十进制数 xxx ，我们如何编写程序来访问其所有的数位 ？ 也许有点困难？那我们不妨从更简单的问题开始。 假设 x=114514x = 114514x=114514 ，我们如何取出 xxx 的个位？ 你可能会不屑地笑道：就这？随即摆出一条算式： x mod 10=114514 mod 10=4x \\bmod 10 = 114514 \\bmod 10 = 4 xmod10=114514mod10=4 轻易地，你便知道了 xxx 的个位是 444 。 事情正在朝着好的方向发展。于是我继续问道：我们如何取出 xxx 的百位 ？ 你可能正在尝试 x mod 100x \\bmod 100xmod100 ，然而很不幸 x mod 100=14x \\bmod 100 = 14xmod100=14 ，这取出的是最低的两位，包括十位和个位，并不能满足要求。 但如果此时我们让 xxx 整除 101010 ，那么就会发现 x÷10=114514÷10=11451x \\div 10 = 114514 \\div 10 = 11451x÷10=114514÷10=11451 ，个位消失不见了，而所有数位都右移了一位，如十位变成个位、百位变成十位、千位变成百位…… 我们令 x=x÷10=11451x = x \\div 10 = 11451x=x÷10=11451 。这时，有 x mod 10=11451 mod 10=1x \\bmod 10 = 11451 \\bmod 10 = 1xmod10=11451mod10=1 ，我们便可以由此取出 114514114514114514 的十位 111 ，准确的说，是原来 xxx 的十位，现在 xxx 的个位。 这样一来，一个遍历 xxx 所有数位的算法便初步成型了： 计算 x % 10 ，取出 x 的最低位。 令 x = x / 10 ，使 x 的数位整体右移，并使 x 的最低位消失。 若 x == 0 则停止，否则回到第 1 步，继续执行。 通过以上步骤，我们可以使 xxx 的数位每次右移 一位，同时取出当前 xxx 的最低位 。这样，我们便可以取出 xxx 的所有数位了。 请运行以下代码片段，观察输出结果，思考运作原理。 int x = 114514; while (x &gt; 0) { printf(&quot;%d %d\\n&quot;, x, x % 10); x = x / 10; } 然而到这里，我们似乎离主线越来越远。如何才能实现十进制转 mmm 进制呢？ 我们现在能够取出 xxx 在十进制下的所有数位，那么类似地，我们也能够取出 xxx 在 mmm 进制下的所有数位： x % 10 取出的是十进制下的最低位；x % m 取出的则是 mmm 进制下的最低位。 令 x = x / 10 可以使 x 在十进制下整体右移一位；令 x = x / m 则可以使 x 在 mmm 进制下右移一位。 于是，我们也可以写出以下代码： int x = 114514; int m = 2; while (x &gt; 0) { printf(&quot;%d\\n&quot;, x % m); x = x / m; } 运行以上代码片段，不难发现，该程序从低到高输出了 xxx 在二进制下的数位。 到此，我们便实现了从十进制到 mmm 进制的转换。 最初的问题： n 进制转换为 m 进制 正如 Hint 中所述，我们可以以十进制数为桥梁，先将 nnn 进制数转换为十进制数，再将十进制数转换为 mmm 进制数，以实现 nnn 进制到 mmm 进制的转换。 最后的回响：代码实现细节 对于 nnn 进制数，我们可以使用字符串将其读入，把它当作字符串来处理。 可以发现，十进制数转换为 mmm 进制数时，数位是从低到高出来的，不满足我们从左到右先写高位再写低位的要求。为此，我们可以先用数组把数位都存起来，最后再倒序输出。 当需要转换的数为 000 的时候，可能会出现小小的问题，请注意判断。 示例代码 #include &lt;stdio.h&gt; #include &lt;string.h&gt; char x[100005]; char sta[100005]; int main() { int T = 0; scanf(&quot;%d&quot;, &amp;T); for (int G = 1; G &lt;= T; G++) { int n = 0, m = 0; scanf(&quot;%s%d%d&quot;, x, &amp;n, &amp;m); long long t = 0; // n 进制转换为十进制 int len = strlen(x);//这里用到了一点扩展知识，将在字符串一章学到 //你也可以在上面按照读取多个字符的方式读取，以空格结尾 for (int i = 0; i &lt; len; i++) { int w = x[i]; if ('0' &lt;= w &amp;&amp; w &lt;= '9') w -=48; else w -= 55; t = t * n + w; // 这里可解读为 t 在 n 进制下左移了一位，并加上最低位 w ，与上文中的式子等价 } // 0 的特殊判断（可以尝试去掉，看看会发生什么） if (t == 0) { printf(&quot;0\\n&quot;); continue; } // 十进制转换为 m 进制 int top = 0; while (t &gt; 0) { int u = t % m; if (u &lt;= 9) u += 48; else u += 55; top++; sta[top] = u; t /= m; } // 倒序输出 while (top &gt; 0) { printf(&quot;%c&quot;, sta[top]); top--; } printf(&quot;\\n&quot;); } return 0; } J LOVE 2000！ 难度 考点 4 异或、贪心 题目分析 有 nnn 个二进制下最高位互不相同的正整数，要求从中取出一些数，使其异或和最大。 1≤n≤311 \\leq n \\leq 311≤n≤31 我们从 x1x_1x1​ 到 xnx_nxn​ 依次考虑是否要将某个数选入答案集合。 假设当前考虑的是：是否要将 xux_uxu​ 选入答案集合，并且当前已经选出的数异或和为 MMM 。若选中 xux_uxu​ ，则令 M=M⊕xuM = M \\oplus x_uM=M⊕xu​ ；若不选，则 MMM 保持不变。 假设 xux_uxu​ 的二进制最高位为第 ttt 位。不难发现，由于这 nnn 个数二进制下最高位互不相同，而 x1,x2,⋯ ,xu−1x_1, x_2, \\cdots, x_{u - 1}x1​,x2​,⋯,xu−1​ 已经被决策完了（决策的是是否要被选入答案集合），那么 xux_uxu​ 就是剩下的数中唯一一个可以改变 MMM 二进制下第 ttt 位的数（xu+1,xu+1,⋯ ,xnx_{u + 1}, x_{u + 1}, \\cdots, x_{n}xu+1​,xu+1​,⋯,xn​ 的第 ttt 位均为 000 ，即使选中也无法改变 MMM 第 ttt 位的值 ）。 如果 MMM 的第 ttt 位为 000 ，那么选中 xux_uxu​ ，令 M=M⊕xuM = M \\oplus x_uM=M⊕xu​ ，就可以使第 ttt 位变成 111 ，这能使 MMM 更大，必然要选 xux_uxu​ 。这时可能会有人产生疑惑，虽然选中 xux_uxu​ 并不改变比第 ttt 位更高的数位，但可能会改变比第 ttt 位更低的数位，从而影响后面的决策，怎么保证这是最优的呢？如果我们此时放弃 xux_uxu​ ，那么第 ttt 位就永远是 000 了，后面再怎么优化，即使第 000 位到第 t−1t - 1t−1 位全部都变成 111 ，也大不过第 ttt 位这一个 111 。因此，如果此时我们能够使 MMM 的第 ttt 位由 000 变为 111 ，那么肯定要变，这样一定是最优的。 如果 MMM 的第 ttt 位为 111 ，那么同样的道理，我们是肯定不会选中 xux_uxu​ 的。 综上，我们可以从 x1x_1x1​ 到 xnx_nxn​ 依次考虑是否选中，即从高位到低位考虑。若 M⊕xu&gt;MM \\oplus x_u &gt; MM⊕xu​&gt;M ，则选中 xux_uxu​ ，令 M=M⊕xM = M \\oplus xM=M⊕x ；否则不选。最后的 MMM 即为最大异或和。 通过这题可以发现，高位在数值大小上是“碾压”低位的。这种从高位到低位依次考虑的思路在程序设计中是常见的。 示例代码 #include &lt;stdio.h&gt; int x[50], a[50]; int main() { int n = 0, m = 0; scanf(&quot;%d&quot;, &amp;n); for (int i = 1; i &lt;= n; i++) scanf(&quot;%d&quot;, &amp;x[i]); int res = 0; for (int i = 1; i &lt;= n; i++) if ((res ^ x[i]) &gt; res) { res ^= x[i]; a[++m] = x[i]; } printf(&quot;%d\\n&quot;, m); for (int i = 1; i &lt;= m; i++) printf(&quot;%d &quot;, a[i]); printf(&quot;\\n%d\\n&quot;, res); return 0; } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C3 - Solution-24航C","link":"https://github.pansis.site/post/C3 - Solution-24航C/","stats":{"text":"22 min read","time":1273000,"words":4811,"minutes":22},"dateFormat":"2024-10-10"},{"content":"A 矩形的面积 难度 考点 1 输入输出 浮点数 题目分析 本题需要先读取四个浮点数，即scanf(&quot;%lf%lf%lf%lf&quot;,&amp;xa,&amp;ya,&amp;xb,&amp;yb); 将两点坐标读入后计算矩形面积 (xb-xa)*(yb-ya) ,并用 printf 输出，保留两位小数用%.2f 在本课程中，为了保证计算精度，浮点数一律使用double，请勿使用 float （主要因为测试点均是在double精度下得出） 示例代码 #include &lt;stdio.h&gt; int main() { double xa, ya, xb, yb; scanf(&quot;%lf%lf%lf%lf&quot;, &amp;xa, &amp;ya, &amp;xb, &amp;yb); printf(&quot;%.2f&quot;, (xb - xa) * (yb - ya)); //先计算出(xb-xa)*(yb-ya)的值然后输出。 return 0; } B 及格比例 难度 考点 1 输入输出，赋值，类型转换 题意分析 此题要求我们由555个int类型的量计算出111个浮点数答案，因而涉及到类型转换。处理方法有： (1) 在等号右侧的最开始加上(double); (2) 在等号右侧引入一个double类型的量，比如乘以1.01.01.0。 如果不进行上述操作之一，则由于等号右侧只涉及int类型的量，无法得到double类型的正确答案。 输出时，以 %.2f 的格式控制符将double类型的量保留222位小数输出；用%%的格式控制符输出单个百分号。 示例代码 #include&lt;stdio.h&gt; int main() { int a, b, c, d, f; double ans; scanf(&quot;%d%d%d%d%d&quot;, &amp;a, &amp;b, &amp;c, &amp;d, &amp;f); ans = (double)(a + b + c + d) / (a + b + c + d + f); printf(&quot;%.2f%%&quot;, ans * 100); return 0; } C 爱城壁 难度 考点 1 输入输出，赋值，类型转换 题意分析 对于此类“输入组数不确定”的情况，宜采用while (scanf()!=EOF)的方式，实现“读到读完为止”的效果。 由于aaa和bbb的乘积可能超出int范围，应当用long long类型的变量存储结果。此处同样涉及类型转换的问题，解决方法有： (1) 在等号右侧的最开始加上(long long); (2) 在等号右侧引入一个long long类型的量，比如乘以1LL，它代表long long类型下的数值111。 (3) 把a,ba,ba,b也定义成long long类型，同时scanf的控制符也相应地改成%lld。 示例代码 #include&lt;stdio.h&gt; int main() { int a,b; long long c; while (scanf(&quot;%d%d&quot;,&amp;a,&amp;b)!=EOF) { c=(long long)a*b; printf(&quot;%lld\\n&quot;,c); } return 0; } D 加密科学 难度 考点 1 字符型变量 题目分析 本题输入若干个字符，要求将字符串的小写元音字母转化为 t ，大写元音字母转化为 T ，其他符号不变，并原样输出。 示例代码 #include &lt;stdio.h&gt; int main() { char c; while (scanf(&quot;%c&quot;, &amp;c) != EOF) { if (c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') { printf(&quot;T&quot;); } else if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' ) { printf(&quot;t&quot;); } else { printf(&quot;%c&quot;, c); } } return 0; } E 小宇的乱码书信 难度 考点 2 ASCII码 题目分析 本题要求对读入的字符进行转换，分为数字和小写字母两种情况，因此需要我们先对字符类型进行判断。 如果字符为数字，则表达式 s&gt;='0' &amp;&amp; s&lt;='9' 为真。 如果字符为小写字母，则表达式 s&gt;='a' &amp;&amp; s&lt;='z' 为真。 另外也可以使用 ctype.h 库中的 isdigit() 和 islower() 两个函数分别判断是否为数字或小写字母。 根据题目给出的翻转规则，我们可以得出下面两个表达式。 如果字符s为数字，则翻转后的字符表达式为'9'-(s-'0')。 如果字符s为小写，则翻转后的字符表达式为'z'-(s-'a')。 公式解释： 通过表达式 s-'0' 我们可以得到 sss 与 '0' 的距离，也就是 sss 对应的数字的值。 因此表达式 '9'-(s-'0') 所得到的就是翻转后的字符的ASCII码值。 参考代码 #include &lt;stdio.h&gt; int main() { char s; while ((s = getchar()) != EOF)//每次循环读取单个字符，直至输入结束 { if (s &gt;= 'a' &amp;&amp; s &lt;= 'z')//如果s是小写字母 { printf(&quot;%c&quot;, 'z' - (s - 'a')); } else if (s &gt;= '0' &amp;&amp; s &lt;= '9')//如果s是数字 { printf(&quot;%c&quot;, '9' - (s - '0')); } else {//如果不是数字、小写字母，依据题意只能是空格 printf(&quot; &quot;); } } } F 小宇算圆周率 难度 考点 3 循环 题目分析 这道题主要考察循环和浮点数的计算。 ∑n=0∞(−1)n2n+1\\sum_{n=0}^{\\infty}\\frac{(-1)^n}{2n+1}∑n=0∞​2n+1(−1)n​ 公式中数列符号与 nnn 的奇偶性有关。我们可以采取使用条件语句进行奇偶性判断。我们也可以采用示例代码中的方法，定义变量sign ,初始值为 111 ，每一次循环结束时，signsignsign 变量自乘 −1-1−1 。 这样即可作为第 nnn 项的符号。 在整数除以整数时，如果想要得到浮点数，一定要在表达式中乘以 1.01.01.0 ，使表达式的计算范围扩展到浮点数范围。 这道题 nnn 的范围是 $1\\le n \\le 10^{5} $ 。在公式 222 中需要计算 (2n+1)2(2n+1)^2(2n+1)2 ，当 nnn 接近给定范围的上限时，(2n+1)2(2n+1)^2(2n+1)2 的大小会超过 int 范围。所以需要使用表达式((2ll * i + 1) * (2 * i + 1)) 进行计算，其中的 2ll 可以使表达式在 long long 范围内计算。 示例代码 #include&lt;stdio.h&gt; #include &lt;math.h&gt; int main() { int t; int n; scanf(&quot;%d&quot;, &amp;t); while (t--) {//循环t次 scanf(&quot;%d&quot;, &amp;n); double col1 = 0; int sign = 1; for (int i = 0; i &lt; n; ++i) { col1 += (1.0 * sign / (2 * i + 1)); sign *= -1; } col1 *= 4; //计算公式1的pi double col2 = 0; for (int i = 0; i &lt; n; ++i) { col2 += (1.0 / ((2ll * i + 1) * (2 * i + 1))); } col2 = sqrt(8 * col2); //计算公式2的pi printf(&quot;%.6f\\n&quot;, fabs(col1 - col2));//输出，保留6位小数。注意浮点数绝对值使用fabs函数 } } G 小牛与基物实验 难度 考点 2-3 循环、浮点数运算 题目分析 本题目涉及多组给定组数据的输入和处理，一般采用循环的方式进行输入： scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { scanf(&quot;%d&quot;, &amp;a[i]); } 在成功输入了数据后，我们就可以对这些数据进行处理了，用sumx表示∑i=1nxi\\sum\\limits_{i=1}^n x_ii=1∑n​xi​，sumy表示∑i=1nyi\\sum\\limits_{i=1}^n y_ii=1∑n​yi​,sumxy表示∑i=1nxiyi\\sum\\limits_{i=1}^n x_iy_ii=1∑n​xi​yi​,sumxx表示∑i=1nxi2\\sum\\limits_{i=1}^n x_i^2i=1∑n​xi2​，vx表示xˉ\\bar xxˉ，vy表示yˉ\\bar yyˉ​。 这里又设计到了多组数据的求和运算，所以我们再用一次循环将他们的结果全部求出，最后代入题目给定的公式进行运算即可。 最后在输出结果的时候注意题目要求保留3位小数，就需要使用%.3lf的占位形式输出。 示例代码1 //使用第一个公式 #include&lt;stdio.h&gt; int n; double x[105], y[105], sumx, sumy, sumxy, sumxx, vx, vy, a, b;//将变量在全局定义会自动初始化为0 int main() { scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { scanf(&quot;%lf%lf&quot;, &amp;x[i], &amp;y[i]); } //进行公式中各个量的计算 for (int i = 0; i &lt; n; i++) { sumx += x[i]; sumy += y[i]; sumxy += x[i] * y[i]; sumxx += x[i] * x[i]; } b = (n * sumxy - sumx * sumy) / (n * sumxx - sumx * sumx);//代入题目公式 vx = sumx / n; vy = sumy / n; a = vy - b * vx; printf(&quot;%.3lf %.3lf&quot;, b, a);//保留3位小数 return 0; } 示例代码2 //代码1优化 /* 可以注意到代码1中的两个循环对次序的要求是一模一样的，所以我们可以将他们合并，就可以抛弃数组，优化内存 */ #include&lt;stdio.h&gt; int n; double x, y, sumx, sumy, sumxy, sumxx, vx, vy, a, b; int main() { scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { scanf(&quot;%lf%lf&quot;, &amp;x, &amp;y); sumx += x; sumy += y; sumxy += x * y; sumxx += x * x; } b = (n * sumxy - sumx * sumy) / (n * sumxx - sumx * sumx); vx = sumx / n; vy = sumy / n; a = vy - b * vx; printf(&quot;%.3lf %.3lf&quot;, b, a); return 0; } 示例代码3 //使用第二个公式 #include&lt;stdio.h&gt; int n; double x, y, sumx, sumy, sumxy, sumxx, vx, vy, a, b, vxy, vxx; int main() { scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { scanf(&quot;%lf%lf&quot;, &amp;x, &amp;y); sumx += x; sumy += y; sumxy += x * y; sumxx += x * x; } vx = sumx / n; vy = sumy / n; vxy = sumxy / n; vxx = sumxx / n; b = (vxy - vx * vy)/(vxx - vx * vx); a = vy - b * vx; printf(&quot;%.3lf %.3lf&quot;, b, a); return 0; } H 煎蛋题 难度 考点 2 简单的运算符 题目分析 本题加了一些限制，锅中的鸡蛋必须完全煎熟后才可以出锅，所以不用考虑太多时间优化的问题。 nnn 个鸡蛋，一次最多煎 mmm 个，共需要煎 ⌈nm⌉\\lceil \\frac{n}{m}\\rceil⌈mn​⌉ 次，每一次需要煎 (a+b)(a+b)(a+b) 分钟，共 ⌈nm⌉⋅(a+b)\\lceil \\frac{n}{m}\\rceil\\cdot(a+b)⌈mn​⌉⋅(a+b) 分钟。 示例代码 #include &lt;stdio.h&gt; int main(){ int n,m,a,b; scanf(&quot;%d%d%d%d&quot;,&amp;n,&amp;m,&amp;a,&amp;b); printf(&quot;%d&quot;,((n+m-1)/m*(a+b))); return 0; } I tux 的 GPA 计算 难度 考点 4 循环，if语句，浮点数计算 题目分析 每次读入typetypetype, SSS，根据相应规则读入成绩，计算出课程绩点 PPP。将学分 SSS 累加到一个浮点数变量 AAA，将学分与课程绩点的乘积 S∗PS * PS∗P 累加到另一个浮点数变量 BBB，结果的 GPA 值即为 B/AB / AB/A。 在计算课程绩点时，如果读入的百分制成绩 XXX 为整型变量，应使用G = 4.0 - 3.0 * (100 - X) * (100 - X) / 1600;或者G = 4 - (double)3 * (100 - X) * (100 - X) / 1600; 来转换为double类型。注意：即使被赋值的变量 G 是double类型，也应该做这样的转换，因为赋值运算会先计算右侧的值再赋值给左侧，假如右侧均为int类型，那么表达式中的除号也会被认为是整除。 示例代码 #include &lt;stdio.h&gt; int main() { int n, type, X, Y; double S, ans = 0, cnt = 0, G; scanf(&quot;%d&quot;, &amp;n); for(int i = 1; i &lt;= n; ++i) { scanf(&quot;%d%lf&quot;, &amp;type, &amp;S); if(type == 0) { scanf(&quot;%d&quot;, &amp;X); if(X &lt; 60) G = 0; else G = 4.0 - 3.0 * (100 - X) * (100 - X) / 1600; } else { scanf(&quot;%d&quot;, &amp;Y); if(Y == 5) G = 4; else if(Y == 4) G = 3.5; else if(Y == 3) G = 2.8; else if(Y == 2) G = 1.7; else G = 0; } ans = ans + G * S; cnt = cnt + S; } printf(&quot;%.2lf\\n&quot;, ans / cnt); return 0; } J 一起来造飞机！ 难度 考点 3 循环 题目分析 本题对于一个较长数列，进行多组询问。每组询问要求给出 [l,r][l,r][l,r] 的区间和。 本题是标准的前缀和模板题，前缀和算法的详细步骤已在题目的 Hint 中给出，这里不再赘述。 代码 #include&lt;stdio.h&gt; int main(){ long long n,t,c[200050],sum[200050],l,r; scanf(&quot;%lld%lld&quot;,&amp;n,&amp;t); sum[0]=0; for(int i=1;i&lt;=n;++i){ scanf(&quot;%lld&quot;,&amp;c[i]); sum[i]=sum[i-1]+c[i]; } for(int i=1;i&lt;=t;++i){ scanf(&quot;%lld%lld&quot;,&amp;l,&amp;r); printf(&quot;%lld &quot;,sum[r]-sum[l-1]); } return 0; } 注意: 编写代码时要预估可能出现的数据的最大值，本题数据点全都取到边界时会出现 4×10104×10^{10}4×1010，故要开 long long。 K 【预习】二进制转换 难度 考点 1 短除法，进制转换理解 题目分析 本题只需对 114114114 做短除法即可，过程如下： 余数 2| 114 ...... 0 ---------------- 2| 57 ...... 1 -------------- 2| 28 ...... 0 ------------ 2| 14 ...... 0 ---------- 2| 7 ...... 1 -------- 2| 3 ...... 1 ------ 2| 1 ...... 1 ---- 0 易得其二进制表示是 1110010 ，本题要求8位二进制 输出，因此应补充前导0，输出 01110010 。 示例代码 #include&lt;stdio.h&gt; int main() { printf(&quot;01110010&quot;); return 0; } 扩展阅读 本题直接输出结果即可，那么对于任意十进制数 nnn，转换为 kkk 进制，我们只需要用程序设计语言模拟上述过程，这是我们下节课的内容，实现逻辑有以下几点： 不断对 nnn 做除法取余数，将余数存进数组里，直到 nnn 变成 000 ； 将存储的余数反着输出。例如短除法过程中我们用 a[0] 到 a[m] 依次存储了所有的余数，那么我们应该反过来输出，从 a[m] 开始输出，一直到 a[0] 结束。 ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"E2 - Solution-24航C","link":"https://github.pansis.site/post/E2 - Solution-24航C/","stats":{"text":"15 min read","time":853000,"words":3170,"minutes":15},"dateFormat":"2024-10-10"},{"content":"A 可靠性估计 难度 考点 1 浮点数、转义字符% 题目分析 题目很容易理解，nnn 和 aaa 都在int范围内（也不会超过double的表示范围），注意是计算次品率而不是正品率。 如果 nnn 和 aaa 都设为了int型，那么在计算的时候要进行类型转换：(double)(n - a) / n或1.0 * (n - a) / n；如果 nnn 和 aaa 都设为了double型，那么直接计算即可。 转义字符%的输出：printf(&quot;%%&quot;)（输出一个%）；double保留两位小数输出：printf(&quot;%.2f&quot;, a)。 示例代码1 #include &lt;stdio.h&gt; int main() { double n, a; scanf(&quot;%lf%lf&quot;, &amp;n, &amp;a); printf(&quot;Result: %.2f%% of the parts are defective.&quot;, 100 * (n - a) / n); return 0; } 示例代码2 #include &lt;stdio.h&gt; int main() { int n, a; scanf(&quot;%d%d&quot;, &amp;n, &amp;a); printf(&quot;Result: %.2f%% of the parts are defective.&quot;, 100.0 * (n - a) / n); return 0; } 示例代码3 #include &lt;stdio.h&gt; int main() { int n, a; scanf(&quot;%d%d&quot;, &amp;n, &amp;a); printf(&quot;Result: %.2f%% of the parts are defective.&quot;, (double)100 * (n - a) / n); return 0; } B A 计划 难度 考点 2 不定组输入，数据类型，EOF 题目分析 这就是上次上机的 a + b 问题的升级版，我们遇到了两个问题： 如何处理不定组输入？注意到题目已经在 Hint 部分给出了提示，只需要使用给出的模板即可。示例代码也可以给我们一些启示：scanf 是具有返回值的函数，当它遇到文件末尾，或者（Windows 下）终端输入 Ctrl+Z 时，就会返回一个 EOF (End Of File) 值。 如何处理数据溢出的问题？我们知道，int 类型变量的数据范围是 [−231,231−1][-2^{31},2^{31}-1][−231,231−1]，而 long long 类型变量的数据范围是 [−263,263−1][-2^{63}, 2^{63}-1][−263,263−1]。对于保证为 int 范围内的输入，我们只需要定义 long long 类型的变量进行存储、计算即可。注意 scanf 和 printf 读写 long long 类型需要使用 %lld。 示例代码 #include &lt;stdio.h&gt; int main() { long long a, b, res; while(scanf(&quot;%lld%lld&quot;, &amp;a, &amp;b) != EOF) { res = a + b; printf(&quot;%lld\\n&quot;, res); } return 0; } 补充说明 你也可以通过类型转换的方式，读取两个 int 型变量，然后输出时转换为 long long 变量。具体来说，代码如下： #include &lt;stdio.h&gt; int main() { int a, b; while(scanf(&quot;%d%d&quot;, &amp;a, &amp;b) != EOF) { printf(&quot;%lld\\n&quot;, 1LL*a+b); //LL跟在数字后面，表示这是一个 long long 型数。 } return 0; } C Firefly小姐的大小写转换 难度 考点 2 ASCII码，多组数据输入 题目分析 本题要求逐个读入字符，并以所有的大写字母转换为小写字母，小写字母转换为大写字母，其余的字符保持不变的规则进行处理。 使用不定组数据输入的方法逐个读入。由于26个字母的ASCII码是连续的，可以使用 c + 'A' - 'a' 和 c + 'a' - 'A' 语句进行大小写转换，非字母不作处理直接输出即可。 示例代码 #include &lt;stdio.h&gt; int main() { char c; while (scanf(&quot;%c&quot;, &amp;c) != EOF) { // 多组数据输入 if ('a' &lt;= c &amp;&amp; c &lt;= 'z') { // 小写转大写 printf(&quot;%c&quot;, c + 'A' - 'a'); }else if ('A' &lt;= c &amp;&amp; c &lt;= 'Z'){ // 大写转小写 printf(&quot;%c&quot;, c + 'a' - 'A'); }else { printf(&quot;%c&quot;, c); } } return 0; } D 浮点平均数的计算 难度 考点 2 循环，分支 题目分析 本题给定一个浮点数数列 ana_nan​，要求计算数列的平均数，并按照输入顺序依次输出 ana_nan​ 中大于等于平均数的值。首先求和并求取平均数，再将读入的数据依次与平均数比较，输出结果，注意用于求和的变量要先初始化。 注意保留三位小数的实现方式。 #include&lt;stdio.h&gt; int main() { double a[200050], sum = 0; int n; scanf(&quot;%d&quot;, &amp;n); for (int i = 1; i &lt;= n; ++i) { scanf(&quot;%lf&quot;, &amp;a[i]); sum += a[i]; } sum = sum / n; printf(&quot;%.3f\\n&quot;, sum); for (int i = 1; i &lt;= n; ++i) { if (a[i] &gt;= sum) { printf(&quot;%.3f &quot;, a[i]); } } return 0; } E s7h想过生日！ 难度 考点 2 循环，分支 题目分析 本题要求求解第 ttt 年后的第 nnn 个闰年，其中保证输入的 ttt 是个闰年。 由于本题数据规模较小，可以从 ttt 一年一年往后找，每到一个闰年就把计数器 cntcntcnt 加一，直到 cnt=ncnt=ncnt=n 时跳出循环 。 由于闰年都是 444 的倍数，也可以从 ttt 往后四年四年的找，代码如下。 代码 #include&lt;stdio.h&gt; int n,t,cnt,year; int main(){ scanf(&quot;%d%d&quot;,&amp;t,&amp;n); year = t; cnt = 0; while(cnt != n) { year += 4; if(year % 400 == 0 || year % 100 != 0) { ++cnt; } } printf(&quot;%d&quot;,year); return 0; } 扩展阅读 我们根据闰年规则，可以确认“每400年有97个闰年”。因此，我们可以首先确认起始年份是400年中的第几个闰年，并将这个数记为 xxx，然后加上 nnn ，根据上述规则直接计算出年份。这种方法不需要循环即可求解。 #include &lt;stdio.h&gt; int main() { int t, n; scanf(&quot;%d%d&quot;, &amp;t, &amp;n); int years = t - t % 400;//求最近的400倍数年份，向下取整 int x = t % 400 / 4;//第x个闰年，首个闰年记为第0个 if (x &lt;= 24) { x -= 0; //(400k)年是闰年 } else if (26 &lt;= x &amp;&amp; x &lt;= 49) { x -= 1; //(400k+100)年不是闰年 } else if (51 &lt;= x &amp;&amp; x &lt;= 74) { x -= 2; //(400k+200)年不是闰年 } else { x -= 3; //(400k+300)年不是闰年 }//x不可能是25、50、75，想想为什么？ int sum = x + n; years += (sum / 97 * 400); //过了多少个400年 sum = sum % 97; //余数 if (sum &lt;= 24) { years += sum * 4; } else if (24 &lt; sum &amp;&amp; sum &lt;= 48) { years += sum * 4 + 4; //(400k+100)年不是闰年 } else if (48 &lt; sum &amp;&amp; sum &lt;= 72) { years += sum * 4 + 8; //(400k+200)年不是闰年 } else if (72 &lt; sum &amp;&amp; sum &lt;= 96) { years += sum * 4 + 12; //(400k+300)年不是闰年 } printf(&quot;%d&quot;, years); } F 学院路停车费 难度 考点 2 计算时间，不定组数据输入，scanf 题目分析 根据 Hint，读入一组数据的代码是 scanf(&quot;%d:%d-%d:%d&quot;, &amp;h1, &amp;m1, &amp;h2, &amp;m2)，读入多组数据只需要按如下代码编写即可： while(scanf(&quot;%d:%d-%d:%d&quot;, &amp;h1, &amp;m1, &amp;h2, &amp;m2) != EOF) { // 处理每组数据 } 可以把 时钟:分钟 的数据均处理为分钟，计算公式为 总分钟数=时钟×60+分钟\\text{总分钟数}=\\text{时钟}\\times60+\\text{分钟}总分钟数=时钟×60+分钟。 两个时刻相差的分钟数就是停放时间 t = (h2 - h1) * 60 + m2 - m1;。 “不足15分钟的部分不收费”，只需要将停放时间除以 151515，会自动向下取整，即可得到收费的“15min的个数”，然后根据停放时间是否超过三个小时，分别处理。 注意只有当停放时间不足三小时，且收费的“15min的个数”为奇数的时候，需要输出 x.5 的形式，特殊判断即可。 代码示例 - 1 #include &lt;stdio.h&gt; int main() { int h1, m1, h2, m2; while(scanf(&quot;%d:%d-%d:%d&quot;, &amp;h1, &amp;m1, &amp;h2, &amp;m2) != EOF) { int t = (h2 - h1) * 60 + m2 - m1; // 计算停放了多少分钟 int k = t / 15; // 计算停放了多少个15min if(k &gt; 12) // 超过3小时的情况 printf(&quot;%d\\n&quot;, 30 + (k - 12) * 5); // 3小时内的停车费为30元，3小时以外的15min个数为k-12 else // 不足3小时的情况 { if(k % 2 == 1) // k为奇数 printf(&quot;%.1f\\n&quot;, k * 2.5); else // k为偶数 printf(&quot;%d\\n&quot;, k / 2 * 5); } } return 0; } 代码示例 - 2 #include &lt;stdio.h&gt; int main() { int h1, m1, h2, m2; while(scanf(&quot;%d:%d-%d:%d&quot;, &amp;h1, &amp;m1, &amp;h2, &amp;m2) != EOF) { int t = (h2 - h1) * 60 + m2 - m1; // 计算停放了多少分钟 int k = t / 15; if(k &gt; 12) k = 12 + (k - 12) * 2; // 超过3小时部分的时间乘2 if(k % 2 == 1) printf(&quot;%.1f\\n&quot;, k * 2.5); else printf(&quot;%d\\n&quot;, k / 2 * 5); } return 0; } G 简单的一元!二次?方程 难度 考点 222~333 浮点数，求根公式 题目分析 如题所示，我们需要解一个“简单的一元!二次?方程”。 注意a=0,b≠0a=0,b\\neq0a=0,b​=0 时，方程是个一元一次方程。 a=0,b=0a=0,b=0a=0,b=0 时，可能出现无穷解或无解。 a≠0a\\neq0a​=0 时，使用求根公式x=−b±b2−4ac2ax=\\frac{-b\\pm\\sqrt{b^{2}-4ac}}{2a}x=2a−b±b2−4ac​​即可。 示例代码 #include&lt;stdio.h&gt; #include&lt;math.h&gt; signed main(){ int a,b,c; scanf(&quot;%d%d%d&quot;,&amp;a,&amp;b,&amp;c); if(a==0){ // 不是二次方程 if(b==0) { // 不是一次方程 if(c==0){ printf(&quot;infinite solutions\\n&quot;); } else{ printf(&quot;No real root\\n&quot;); } } else{ printf(&quot;%.2f&quot;,(-1.0*c)/b);// bx + c = 0 , 注意类型转换 } } else{ int delta = b*b-4*a*c; if(delta &lt; 0 ){ printf(&quot;No real root\\n&quot;); } else if(delta == 0){ printf(&quot;%.2f\\n&quot;,(-1.0*b)/(2.0*a)); } else { double ans1 = ((-1.0*b) - sqrt(delta))/(2.0*a),ans2 = ((-1.0*b) + sqrt(delta))/(2.0*a);//求根公式 if(ans1&gt;ans2){ double t; t = ans1; ans1 = ans2; ans2 = t; }//注意a的正负性，考虑要不要交换两个解。 printf(&quot;%.2f %.2f\\n&quot;, ans1, ans2); } } return 0; //好习惯 } H BUAA好声音 难度 考点 3 逻辑运算符、转义字符双引号 题目分析 对于不定组数据输入，我们用到while(scanf(&quot;%d%d%d%d%d%d&quot;, x1, x2, x3, y1, y2, y3) != EOF)，同时要看清楚输入的 xxx 和 yyy 到底对应什么数据，不要弄混。 其中的逻辑，我们原样翻译题干即可，可以写出这样的伪代码： if (至少一位导师给出超范围的分数) { //程序出问题了，输出报错信息； } else if (至少一位导师的印象分和表现分都为零，或者没有导师的印象分和表现分都合格) { //该选手淘汰； } else { //只剩下一种情况：至少一位导师的印象分和表现分都合格； //该选手晋级； } 发现了吗？在这样一串if-else结构中，所有可能发生的情况都被讨论到了，没有一个遗漏的。并且以这题为例，中间的else if条件，已经是在排除了第一个if条件的情况下，再分出的条件，即（（没有导师给出超范围的分数），且（至少一位导师的印象分和表现分都为零，或者没有导师的印象分和表现分都合格））。最后一个else，便是上述所有情况除外，另外的所有情况。 逻辑运算符&amp;&amp;表示且，||表示或。需要多个逻辑运算符时请注意运算优先级，如果不确定建议加括号区分。 转义字符&quot;的输出方法为：printf(&quot;\\&quot;&quot;)（输出一个&quot;）。 示例代码 #include &lt;stdio.h&gt; int main() { int x1, x2, x3, y1, y2, y3; while (scanf(&quot;%d%d%d%d%d%d&quot;, &amp;x1, &amp;x2, &amp;x3, &amp;y1, &amp;y2, &amp;y3) != EOF) { if (x1 &gt; 100 || y1 &gt; 100 || x2 &gt; 100 || y2 &gt; 100 || x3 &gt; 100 || y3 &gt; 100) { printf(&quot;Error: \\&quot;BaconToast\\&quot; made a BIG mistake!\\n&quot;); } else if ((x1 == 0 &amp;&amp; y1 == 0) || (x2 == 0 &amp;&amp; y2 == 0) || (x3 == 0 &amp;&amp; y3 == 0)) { printf(&quot;PITY!\\n&quot;); } else if ((x1 &gt;= 60 &amp;&amp; y1 &gt;= 60) || (x2 &gt;= 60 &amp;&amp; y2 &gt;= 60) || (x3 &gt;= 60 &amp;&amp; y3 &gt;= 60)) { printf(&quot;OK!\\n&quot;); } else { printf(&quot;PITY!\\n&quot;); } } return 0; } I 为什么不用程序转义呢 难度 考点 4 转义字符 题目分析 由于没有空格和换行之类的影响输入的东西，所以直接 scanf 即可读入整个字符串。 接下来，在这里列举出来所有你需要处理的需要用转义字符和格式控制字符输出的字符： 待转换字符 转换方式 转换结果 \\ 转义 \\\\ &quot; 转义 \\&quot; % 格式控制 %% 之后逐字符的输出即可。 需要注意的是，由于写代码本身输入这些字符仍然需要转换，所以你还要对转换结果里的字符再转换一下，详见示例代码。 示例代码 #include &lt;stdio.h&gt; int main() { char str[1007]; int j = 0; while (scanf(&quot;%c&quot;, &amp;str[j]) != EOF) { j++; } printf(&quot;printf(\\&quot;&quot;);//别忘了格式要求 for (int i = 0; i &lt; j; i++) { //未到字符串结尾时持续遍历 if (str[i] == '\\\\') { printf(&quot;\\\\\\\\&quot;); } else if (str[i] == '\\&quot;') { printf(&quot;\\\\\\&quot;&quot;); } else if (str[i] == '%') { printf(&quot;%%%%&quot;); } else { //注意'/'不需要被转义 printf(&quot;%c&quot;, str[i]); } } printf(&quot;\\&quot;);&quot;);//别忘了格式要求 return 0; } Extra 理论上说，当你按照以上提示完善该程序后，对于所有处理转义字符的情况，你都可以立马得出答案了，所以这个题实际上是帮助大家省事的（ J -M3rcury-的石子游戏 难度 考点 4 贪心 题目分析 记所有石子的数目和为 SSS ，石子数目最多的一堆里有 MMM 个石子 考虑如果最多的一堆有太多的石子，游戏进行的轮次一定少 因此有这样一种贪心策略：每次都从最多的两堆石子中各取一个，则 如果 M&gt;S−MM&gt;S-MM&gt;S−M ，游戏至多进行 S−MS-MS−M 轮 如果 M≤S−MM \\leq S-MM≤S−M ，那么我们按照这样的一种游戏策略，最后一定能进行到全取完或者剩一个，那么此时的答案就是 ⌊S2⌋\\lfloor {\\frac{S}{2}} \\rfloor⌊2S​⌋ 示例代码 #include &lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;, &amp;n); long long sum = 0, maxn = 0; for (int i = 1; i &lt;= n; i++) { long long x; scanf(&quot;%lld&quot;, &amp;x); sum += x; if (x &gt; maxn) maxn = x; } if (maxn &gt; sum / 2) printf(&quot;%lld\\n&quot;, sum - maxn); else printf(&quot;%lld\\n&quot;, sum / 2); return 0; } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C2 -Solution-24航C","link":"https://github.pansis.site/post/C2 -Solution-24航C/","stats":{"text":"16 min read","time":935000,"words":3491,"minutes":16},"dateFormat":"2024-09-26"},{"content":"A 可靠性估计 题解 #include &lt;stdio.h&gt; int main() { int n,a; scanf(&quot;%d%d&quot;,&amp;n,&amp;a); printf(&quot;Result: %.2f%% of the parts are defective.&quot;,100*(1-1.0*a/n)); } printf 1.库： #include &lt;stdio.h&gt; 2、用法： printf(&quot;格式说明字符串&quot;,...,...,...); ....为参数表，与字符串中的%一一对应 3、转义字符 \\n 换行符 \\\\ 反斜线 ? 问号 \\' 单引号 \\&quot; 双引号 %% %号 （仅限于scanf和printf中会转义） 4.printf()常用字段类型说明符 %d： － 以十进制方式输出带标记整数（正数默认不输出标记）； int 十进制 ////////////////////////////// %u： － 以十进制方式输出无标记整数； unsigned int 十进制 ///////////////////// %x — 以十六进制方式输出整数 int 十六进制 %o： － 以八进制方式输出整数 int 八进制 %lf： － 以小数方式输出单、双精度实数； double 小数方式 ///////////////////// %e： － 以指数方式输出单、双精度实数； double 科学计数法 %g： － 以%f，%e中较短的输出宽度输出单、双精度实数； double %c： － 输出单个字符； char //////////////////////////////////// %s： － 输出字符串。 char* /////////////////////// %lld -以十进制方式输入长整型 （linux系统） long long /////////////////////////// %llu 以十进制方式输出unsigned long long unsigned long long ///////////////////////// 4.完整的字段说明序列 %[flags] [width] [.precision] type flags 格式标志 width字段宽度 .precision精度 type类型(必填) 5.flags 格式标志 标志 意义 缺省 结果左对齐，右边填空格 结果右对齐 在有符号的数据前输出标记(正号或负号) 只对负数加符号 0 数据在字段宽度内加前导0 不加前导0 \\# 对八进制和十六进制数（o,x,X）显现前缀O,Ox 不加前缀 对类型符e,f,g强制输出小数点 只在有小数值时输出小数点 6.width字段宽度 在%与字母之间 插入 整数 代表该字段的最小宽度 7.precision精度 在%与字母之间 插入 小数 代表要保留的小数位数 %.1f //输出保留一位小数 %.2f //输出保留二位小数 (位数不足以零补位) 明确计算范围 只取决于计算符两侧的变量或常量类型，与其他范围完全无关。 计算符两侧均为整数时，若均为int类型，则计算范围为int 计算符两侧均为整数时，若存在long long类型，则计算范围为long long 计算符两侧存在double，则计算范围为double Tips：1、计算范围与赋值范围无关 #include&lt;stdio.h&gt; int main() { long long c; int a=1000000,b=1000000; c=a*b; printf(&quot;%lld&quot;,c); } 2、int两数运算变为long long运算 #include&lt;stdio.h&gt; int main() { long long c; int a=1000000,b=1000000; c=1ll*a*b; c=(long long)a*b; printf(&quot;%lld&quot;,c); } 3、两个整数执行除法 #include&lt;stdio.h&gt; int main() { double c; int a=1,b=3; c=1.0*a/b; c=(double)a/b; } 强制类型转换 区分下面两个写法的区别 (double) 7/5 //得到1.4 (double) (7/5) //得到1.0 B A 计划 不定组输入 1、理解scanf的返回值 很多函数都会有返回值，类似于 y=f(x)y=f(x)y=f(x) ，其中 yyy 就是函数 fff 的返回值。 scanf的返回值代表它成功赋值的变量个数。 当scanf遇到输入结束（可以理解为Ctrl+Z），返回-1.（EOF=-1） 2、不定组输入的模板 while ((scanf(&quot;xxx&quot;,xxx))!=EOF) { } 题解 #include &lt;stdio.h&gt; int main() { int a,b; while ((scanf(&quot;%d%d&quot;,&amp;a,&amp;b))!=EOF) { printf(&quot;%lld\\n&quot;,1ll*a+b); } } C Firefly小姐的大小写转换 char变量类型 本质就是数字，可以进行计算、比较，存储的值对应字符的ASCII 题解 #include &lt;stdio.h&gt; int main() { char c; while (scanf(&quot;%c&quot;,&amp;c)!=EOF) { if (c&gt;='a' &amp;&amp; c&lt;='z') c=c-'a'+'A'; else if (c&gt;='A' &amp;&amp; c&lt;='Z') c=c-'A'+'a'; printf(&quot;%c&quot;,c); } } D 浮点平均数的计算 题解 #include &lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;,&amp;n); double a[2004];//绝对禁止开double a[n] double sum=0; for (int i = 0; i &lt; n; ++i) { scanf(&quot;%lf&quot;,&amp;a[i]); sum+=a[i]; } double avg=sum/n; printf(&quot;%.3f\\n&quot;,avg); for (int i = 0; i &lt; n; ++i) { if (a[i]&gt;=avg) printf(&quot;%.3f &quot;,a[i]); } } E s7h想过生日 ! #include &lt;stdio.h&gt; int main() { int t,n; scanf(&quot;%d%d&quot;,&amp;t,&amp;n); t++; while (n!=0) { if ((t%100==0 &amp;&amp;t%400==0)||(t%100!=0 &amp;&amp;t%4==0)) n--; t++; } t--; printf(&quot;%d&quot;,t); } F 学院路停车费 #include &lt;stdio.h&gt; int main() { int h1,m1,h2,m2; while (scanf(&quot;%d:%d-%d:%d&quot;,&amp;h1,&amp;m1,&amp;h2,&amp;m2)!=EOF) { int min=m2-m1+(h2-h1)*60; int min15=min/15; if (min15&lt;=12) { if (min15%2==0) printf(&quot;%d\\n&quot;,min15/2*5);//不能写成min15*2.5 else printf(&quot;%.1f\\n&quot;,min15*2.5); } else { printf(&quot;%d\\n&quot;,30+(min15-12)*5); } } } H 简单的一元!二次?方程 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main() { int a,b,c; scanf(&quot;%d%d%d&quot;,&amp;a,&amp;b,&amp;c); if (a==0) { if (b==0) { if (c==0) printf(&quot;infinite solutions&quot;); else printf(&quot;No real root&quot;); } else { printf(&quot;%.2f&quot;,-1.0*c/b); } } else { double delta=b*b-4*a*c; if (delta==0) printf(&quot;%.2f&quot;,-1.0*b/(2*a)); else if (delta&lt;0) printf(&quot;No real root&quot;); else { double x1=(-b+sqrt(delta))/(2*a); double x2=(-b-sqrt(delta))/(2*a); if (x1&gt;x2) printf(&quot;%.2f %.2f&quot;,x2,x1); else printf(&quot;%.2f %.2f&quot;,x1,x2); } } } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C2讲解-24航C","link":"https://github.pansis.site/post/C2讲解-24航C/","stats":{"text":"7 min read","time":384000,"words":1353,"minutes":7},"dateFormat":"2024-09-26"},{"content":"一、CISC和RISC 1、CISC：复杂指令集 1、具有大量的指令和寻址方式，指令长度可变，指令功能和CPU设计复杂 2、大多数程序只使用少量的指令就能够运行。 2、RISC：精简指令集 1、只包含最有用的指令，指令长度固定 2、确保数据通道快速执行每一条指令，最好是每一条指令都能够在一个时钟周期内执行完 3、使CPU硬件结构设计变得更为简单 3、两者数据通路 二、ARM体系 1、特点 每条数据处理指令都对算术逻辑单元和移位器控制，实现了ALU和移位器的最大利用； 地址自动增加和减少寻址模式，优化程序循环； 多寄存器装载和存储指令实现最大数据吞吐量; 所有指令的条件执行实现最快速的代码执行。 2、ARM Coxtex A9特性 1、四种指令集状态 ARM状态 Thumb状态 Jazelle状态：指令层次对JAVA加速 ThumbEE状态：使得指令集能特别适用于运行阶段的编码产生（例如实时编译） 2、Thumb指令集 32位体系结构上的16位指令集 以ARM指令集65%的存储空间，换取接近ARM指令集的性能（约有15％左右的性能损失） 16位指令集对应相同功能的32位指令，是ARM指令的子集，在32位的体系结构上实现16位指令集 运行时，16位指令转换成32位，实时解压缩，全32位操作 ARM/THUMB代码可快速切换(子程序调用的时间，3时钟周期) ","tags":[{"index":-1,"name":"嵌入式系统","slug":"AVw14AAoK","used":true,"link":"https://github.pansis.site/tag/AVw14AAoK/"}],"title":"1.1 ARM","link":"https://github.pansis.site/post/1.1 ARM/","stats":{"text":"2 min read","time":92000,"words":429,"minutes":2},"dateFormat":"2024-09-24"},{"content":"A I l0VE Bu@@ C语言基本结构 #include &lt;stdio.h&gt; //引用头文件 int main() //main函数 程序的开始 int代表函数返回值为整形 { printf(&quot;Hello，world&quot;); //printf()函数 //返回值,函数结束位置，main返回值为0代表程序正常结束，否则为异常结束 } #include &lt;stdio.h&gt; int main() { printf(&quot;hEll0,WOrlD!?\\nWeLlC0Me To CH1n@\\nI 10VE Bu@@&quot;); return 0; } 错误写法 #include &lt;stdio.h&gt; int main() { printf(&quot;hEll0,WOrlD!? WeLlC0Me To CH1n@ I 10VE Bu@@&quot;); return 0; } B a+b2024 数据类型 名称 数值范围 功能 char -128~127 表示字符 int −231−(231−1)-2^{31}-(2^{31}-1)−231−(231−1)（十进制下大约是2*10^9） 表示整数 long long −263−(263−1)-2^{63}-(2^{63}-1)−263−(263−1) 表示大整数 double 略 表示实数 unsigned int 0~(2^{32}-1) 表示正整数 unsigned long long 0~(2^{63}-1) 表示大正整数 其他不常用类型：float（单精度浮点数，本课程禁用，一律用double） unsigned char ，short，unsigned short 变量定义方法 &lt;变量类型&gt; &lt;变量名&gt;; int a; int b,c;//一次定义多个变量 long long d; int d=1;//定义的同时赋初值 printf 1.库： #include &lt;stdio.h&gt; 2、用法： printf(&quot;格式说明字符串&quot;,...,...,...); ....为参数表，与字符串中的%一一对应 格式说明字符串 “123123” “%d:123%d” &quot;123\\n123&quot; 3、转义字符 \\n 换行符 &amp;&amp;&amp;&amp;&amp;&amp;&amp; \\\\ 反斜线 &amp;&amp;&amp;&amp;&amp;&amp;&amp; ? 问号 &amp;&amp;&amp;&amp;&amp;&amp; \\' 单引号 &amp;&amp;&amp;&amp;&amp; \\&quot; 双引号 &amp;&amp;&amp;&amp;&amp; %% %号 &amp;&amp;&amp;&amp;&amp; （仅限于scanf和printf中会转义） 4.printf()常用字段类型说明符 %d： － 以十进制方式输出带标记整数（正数默认不输出标记）； int 十进制 ////////////////////////////// %u： － 以十进制方式输出无标记整数； unsigned int 十进制 ///////////////////// %x — 以十六进制方式输出整数 int 十六进制 %o： － 以八进制方式输出整数 int 八进制 %lf： － 以小数方式输出单、双精度实数； double 小数方式 ///////////////////// %e： － 以指数方式输出单、双精度实数； double 科学计数法 %g： － 以%f，%e中较短的输出宽度输出单、双精度实数； double %c： － 输出单个字符； char //////////////////////////////////// %s： － 输出字符串。 char* /////////////////////// %lld -以十进制方式输入长整型 （linux系统） long long /////////////////////////// %llu 以十进制方式输出unsigned long long unsigned long long ///////////////////////// scanf 1.库： #include &lt;stdio.h&gt; 2、用法： scanf(&quot;格式说明字符串&quot;,...,...,...); ....为参数表，与字符串中的%一一对应 3、常用字段类型说明符 （与printf基本一致) 4、示例 5 6 scanf(&quot;%d%d&quot;,&amp;a,&amp;b); 题解 #include &lt;stdio.h&gt; int main() { int a, b,c; scanf(&quot;%d%d&quot;, &amp;a, &amp;b); c = a + b; printf(&quot;%d + %d = %d&quot;, a, b, c); return 0; } #include &lt;stdio.h&gt; int main() { int a, b; scanf(&quot;%d%d&quot;, &amp;a, &amp;b); printf(&quot;%d + %d = %d&quot;, a, b, a + b); return 0; } C A%B Problem #include &lt;stdio.h&gt; int main() { int a,b; scanf(&quot;%d%d&quot;,&amp;a,&amp;b); printf(&quot;%d mod %d == %d&quot;,a,b,a%b); } D 简单的 a / b #include &lt;stdio.h&gt; int main() { int a,b; scanf(&quot;%d%d&quot;,&amp;a,&amp;b); printf(&quot;%d / %d = %d&quot;,a,b,a/b); } E Cirno的助教任务 if 1、语法 if (&lt;表达式&gt;) &lt;语句&gt; else if (&lt;表达式&gt;) &lt;语句&gt; else if (&lt;表达式&gt;) &lt;语句&gt; else if (&lt;表达式&gt;) &lt;语句&gt; ...... else &lt;语句&gt; 1、&lt;表达式&gt;既可以是逻辑表达式也可以是数值 if (a==1){}//a为1时执行{}内语句，反之不执行 if (1){}//永远执行{}内语句 if (0){}//永远不执行{}内语句 if (a){}//a为0时不执行{}内语句，反之执行 2、&lt;语句&gt;含有多个代码时，必须加{} int a=0; if (a==1) printf(&quot;1&quot;); printf(&quot;0&quot;); 3、&lt;语句&gt;含有多个代码时，可以不加{} 4、常见的逻辑运算符 &lt; 小于 &lt;= 小于等于 &gt; 大于 &gt;= 大于等于 == 等于 != 不等于 ! 一元逻辑非运算，把0值变为1，非0值变成0 &amp;&amp; 逻辑与，两端表达式都为非0值时产生1，否则产生0 and || 逻辑或，两端表达式中有1个为非0值时产生1，否则产生0 or if (a==1) if (a&lt;1) if (a&gt;1&amp;&amp;b&lt;1) if (a&gt;1||b&lt;1) if (!(a&gt;1)) 题解 #include &lt;stdio.h&gt; int main() { int s; scanf(&quot;%d&quot;,&amp;s); if (s==9) printf(&quot;bakabaka!\\n&quot;); else if (s&gt;=60) printf(&quot;Pass!\\n&quot;); else printf(&quot;Fail...\\n&quot;); } F Focalors吃通心粉 while循环 1、语法 while (&lt;表达式&gt;) &lt;语句&gt; &lt;表达式&gt;为循环条件，条件成立时进入循环（方式与if完全一致） &lt;语句&gt;含有多个代码时，必须加{} int t, a, b; scanf(&quot;%d&quot;, &amp;t); int i=0; while(i &lt; t) // while循环t次，处理t组数 { // your code i=i+1; } for循环 1、语法 for(&lt;表达式1&gt;;&lt;表达式2&gt;;&lt;表达式3&gt;) &lt;语句&gt; &lt;表达式1&gt;在循环前执行一次，用于初始化变量 &lt;表达式2&gt;为循环条件 &lt;表达式3&gt;每次循环结束执行，一般用于i++ &lt;表达式1&gt;&lt;表达式2&gt;&lt;表达式3&gt;均可省略，但分号不能省略.表达式中有多个语句的用逗号隔开 省略&lt;表达式2&gt;则循环始终进行，除非遇到break int i, t, a, b; scanf(&quot;%d&quot;, &amp;t); for(i=0; i&lt;t; i=i+1) // for循环t次，处理t组数 { // your code } 题解 #include &lt;stdio.h&gt; int main() { int t; scanf(&quot;%d&quot;,&amp;t); while (t--) { int n,m; scanf(&quot;%d%d&quot;,&amp;n,&amp;m); if (m==0) printf(&quot;QAQ\\n&quot;); else printf(&quot;%d\\n&quot;,n/m); } } G 星月图 #include &lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;,&amp;n); while (n--) { int op,a; scanf(&quot;%d%d&quot;,&amp;op,&amp;a); if (op==0) { for (int i = 0; i &lt; a; ++i) { printf(&quot;*&quot;); } } else { for (int i = 0; i &lt; a; ++i) { printf(&quot;( )&quot;); } } printf(&quot;\\n&quot;); } } H Firefly小姐的平均分统计 #include &lt;stdio.h&gt; int main() { int sum=0; int sc; int num=0; while (1) { scanf(&quot;%d&quot;,&amp;sc); if (sc==-1) break; sum+=sc; num++; } printf(&quot;%d&quot;,sum/num); } I 最大公因数 #include&lt;stdio.h&gt; int main(){ int a,b; scanf(&quot;%d%d&quot;,&amp;a,&amp;b); for(int i=1000000;i&gt;=1;i--){ if(a%i==0 &amp;&amp;b%i==0) { printf(&quot;%d\\n&quot;,i); return 0; } } } J 简单的一元三次方程 #include &lt;stdio.h&gt; int main() { int a,b,c; scanf(&quot;%d%d%d&quot;,&amp;a,&amp;b,&amp;c); for (int i = -100; i &lt;=100; ++i) { if (i*i*i+a*i*i+b*i+c==0) { //不能写成i^3+a*i^2+b*i+c==0 printf(&quot;%d &quot;,i); //return 0; } } } 课程注意事项 1、OJ练习分为C系列和E系列 E系列时间在周末，一般周六早上开始，一般持续四到五天。 2、课下练习可以借助GPT等AI功能进行辅助学习，但严禁直接将生成的代码提交，一经发现该次比赛记为0分。 3、严禁使用代码生成插件（包括但不限于GitHub Copilot） 4、有任何疑问，随时在课程群或私信助教。 ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C1讲解-24航C","link":"https://github.pansis.site/post/C1讲解-24航C/","stats":{"text":"9 min read","time":516000,"words":1810,"minutes":9},"dateFormat":"2024-09-20"},{"content":"A I l0VE Bu@@ 难度 考点 1 输出 题目分析 按照题目要求，分三行输出即可，记得直接从页面上复制，行末加上换行符。 示例代码 #include&lt;stdio.h&gt; int main() { printf(&quot;hEll0,WOrlD!?\\n&quot;); printf(&quot;WeLlC0Me To CH1n@ \\n&quot;); printf(&quot;I 10VE Bu@@\\n&quot;); return 0; } B a+b2024 难度 考点 1 scanf printf 题目分析 本题的输入共两个正整数，其数据范围均在 int 范围内，因此我们先定义两个 int 类型的变量 a、 b。随后使用 scanf(&quot;%d%d&quot;,&amp;a ,&amp;b) 将输入中的两个数读入并赋值到变量 a、 b 中。 接着我们根据题目要求进行输出，采用 printf(&quot;%d + %d = %d&quot;,a,b,a+b);。其中第一个参数 &quot;%d + %d = %d&quot; 中的 %d 在代码运行当中会被替换成后面参数的值。在此处，第一个 %d 会被替换成变量 a 的值，第二个 %d 会被替换成变量 b 的值，第三个 %d 会被替换成 a+b 的值。 示例代码 #include &lt;stdio.h&gt; int main() { int a, b; scanf(&quot;%d%d&quot;, &amp;a, &amp;b); printf(&quot;%d + %d = %d&quot;, a, b, a + b); return 0; } C A%B Problem 难度 考点 1 输入输出 题目分析 在 C 语言中，% 被用于表示求余运算，a % b 的结果即为 aaa 除以 bbb 得到的余数。 在 scanf() 和 printf() 函数中，第一个参数都是一个字符串，里面描述了输入/输出的文本内容，起到了格式控制的作用。其中 %d 表示（整型）占位符。每一个占位符与格式控制字符串后面的参数一一对应，起到了“将输入存放到变量”或者“将变量进行输出”的效果。因此，你的格式控制字符串中有 nnn 个占位符，后面就应该跟着 nnn 个参数。 示例代码 #include &lt;stdio.h&gt; int main() { int a, b; scanf(&quot;%d%d&quot;, &amp;a, &amp;b); printf(&quot;%d mod %d == %d&quot;, a, b, a % b); return 0; } 补充说明 关于输入，在使用 scanf() 函数时，若输入的是多个连续的数值型值（整数和实数），则输入的空白符（空格、回车和制表符）都会被忽略。所以这道题虽然是多行输入，但也可以使用 &quot;%d%d&quot; 的参数读入，不需要且不建议使用 &quot;%d\\n%d&quot; 的参数读入。详见教材 2.3.1-2.3.3 节的有关说明。 D 简单的 a/ba/ba/b 难度 考点 1 数学计算 题目分析 本题目为课堂题目，只是改变了输出的格式，按照题目要求，只需要先读入 a,ba,ba,b ，然后计算出 a/ba/ba/b ，最后按照输出格式进行输出即可（注意输出格式中的空格）。 示例代码 #include&lt;stdio.h&gt; int main() { int a, b, ans; scanf(&quot;%d%d&quot;, &amp;a, &amp;b); ans = a / b; printf(&quot;%d / %d = %d&quot;, a, b, ans); return 0; } E Cirno的助教任务 难度 考点 1 选择结构 题目分析 我们每次都读入一个score，根据题目的要求输出对应的字符串即可。 （有些时候你的程序未通过评测可能是因为打错字，所以输出的时候出现了错误的字符，个人给出的建议就是可以选择复制题目描述里的字符串到代码中） 示例代码 #include &lt;stdio.h&gt; int main() { int score; scanf(&quot;%d&quot;, &amp;score); if (score == 9) { // 如果分数恰好等于9 printf(&quot;bakabaka!&quot;); } else if (score &gt;= 60) { // 否则，如果分数大于等于60 printf(&quot;Pass!&quot;); } else { printf(&quot;Fail...&quot;); } return 0; } F Focalors吃通心粉 难度 考点 2 选择结构、循环结构、数学运算 题目分析 根据题意，容易知道我们要用通心粉的总份数除以每天吃掉的通心粉的份数。同时，由于当剩余的通心粉不足 mmm 份的时候，Focalors就要去购买通心粉，所以我们在计算的过程中要对 n/mn/mn/m 下取整。对于除数为零的情况，我们需要在计算之前进行特判。对于多组数据，我们使用循环结构，从而处理多组数据。 示例代码 #include &lt;stdio.h&gt; int main() { int t; scanf(&quot;%d&quot;, &amp;t); for (int i = 0; i &lt; t; i++) { int n, m; scanf(&quot;%d%d&quot;, &amp;n, &amp;m); if (m == 0) printf(&quot;QAQ\\n&quot;); else printf(&quot;%d\\n&quot;, n / m); } return 0; } G 星月图 难度 考点 2 循环、分支 题目分析 对于输入的数据组数 nnn ，我们很自然地想到在最外层写一个循环。可以选择for循环，也可以选择while循环。 在最外层循环的内部再进行输入scanf和处理if, printf每组数据，这样就实现了多组数据的输入处理。 该题需要我们写一个小小的分支，因为 opopop 只有两种情况，我们用if语句判断其中一种，另一种用else处理。 在分支内，还需要我们进行一个循环，依据输入的 aaa ，我们循环输出字符即可。注意：月亮( )是由三个字符组成的，中间有一个空格，如果像宋老师一样没注意这个就PE了！ 最后别忘了格式要求呀，每一行末尾都要换行，不然就PE了·。 示例代码 #include &lt;stdio.h&gt; int main() { int n, op, a; scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { scanf(&quot;%d%d&quot;, &amp;op, &amp;a); if (op == 0) { for (int i = 0; i &lt; a; i++) { printf(&quot;*&quot;); } } else { for (int i = 0; i &lt; a; i++) { printf(&quot;( )&quot;); } } printf(&quot;\\n&quot;); } return 0; } 多组数据输入的扩展 在网上的一些题解中，有关多组数据输入，我们常常能见到如下的代码： int n; scanf(&quot;%d&quot;, &amp;n); while(n--){ //code } while (n--)到底是什么意思？为什么可以循环nnn次？ 其实，括号中的表达式是循环条件，也就是只有满足这个循环条件才会继续循环。考虑这样一个循环while (2 &gt; 0)，因为括号内的语句始终为真，所以循环会一直进行下去，这是一个死循环。 而在C语言中，规定非0为真，0为假。这表示在计算机看来，2 &gt; 0这样一个表达式，其实等价于一个非零的数（在C语言中就是1），因为这个表达式是永真式。正因如此，如果你尝试写一个这样的循环while (1)，它和上面那个循环是等价的，都是死循环。同理， 2 &lt; 0 这样一个表达式等价于 0 ，因此尝试写一个 while (2 &lt; 0) 或者 while(0) 这样的循环它将永远不会进行。 在while (n--)中，为了方便探讨，我们假设 n=3n=3n=3 ，那么第一次循环前，程序先检测 n（此时为3）是否为真，显然为真，那么此时n自减（n变成了2）再进入循环执行里面的其他语句；第二次循环前，先检查n（此时为2）是否为真，依此进行下去；直到循环三次之后，n变成了0，再想继续循环的时候，发现为假不符合条件了，因此循环也就中断了。 同样的，if, for这些语句的括号内也有条件判断，也遵循上面的规则。本题代码中，if (op == 0)可以被等价替换为if (!op)，想想是为什么？ H Firefly小姐的平均分统计 难度 考点 2 循环 题目分析 本题要求统计一组数据的平均数，可以先逐个读入并求和，同时统计数据个数。统计到 −1-1−1 时结束循环，将求和结果除以数据个数即为结果。 注意：C语言中 int 类型变量在进行除法计算时会自动向零取整，具体表现为结果为正时向下取整，结果为负时向上取整。例如： int a = 3, b = 2; printf(&quot;%d&quot;, a / b); 以上代码的输出为 111。故本题直接计算求和结果除以数据个数即可，并不需要因为向下取整引入额外操作。 详细原理将在数据类型章节讲解。 示例代码 #include &lt;stdio.h&gt; int main() { int res = 0; // 统计变量需要赋初始值为0 int a; scanf(&quot;%d&quot;, &amp;a); int n = 0; // 统计变量需要赋初始值为0 while (a != -1) { // 当前读入的数据为-1，结束循环 res += a; // 累加结果 n++; // 统计数据个数 scanf(&quot;%d&quot;, &amp;a); } printf(&quot;%d&quot;, res / n); // 输出 return 0; } I 最大公因数 难度 考点 2 枚举, if语句, 循环 题意分析 枚举法，又有穷举、暴力搜索等称呼，是初学者应当尽早掌握的算法。它可以被总结为下述333个步骤： 1. 确定**所有可能的答案** 2. 确立**检验任意可能的答案是否正确**的判断方法 3. 使用上述判断方法**逐一检验**所有可能的答案进行 本题要求我们求出给定的a,ba,ba,b的最大公因数。我们不妨遵循上述333个步骤： 1. 正确的最大公因数显然一定在[1,a][1,a][1,a]这个范围内。当然，如果说在[1,b][1,b][1,b]范围内，无疑也正确。 2. 正确答案gcdgcdgcd应当是a,ba,ba,b的公因数，即它要满足((a%gcd==0)&amp;&amp;(b%gcd==0))。同时，它还需要是最大的，为实现这一点，我们在[1,a][1,a][1,a]中按从大到小的顺序寻找。 3. 综上，我们需要在[1,a][1,a][1,a]这个范围内,逐一并且从大到小地尝试所有可能的答案，并取其中遇到的第一个，即最大的公因数为正确答案。 为此，可以先令gcd=a;,每检验一个可能的答案后,再执行gcd=gcd-1;,由此便实现了gcdgcdgcd从大到小变化的要求。 示例代码 #include&lt;stdio.h&gt; int a,b,gcd; //定义所需的3个变量 int main() { scanf(&quot;%d%d&quot;,&amp;a,&amp;b); //读入a,b gcd=a; //我们将从a开始，从大到小地检验可能的答案 while (!((a%gcd==0)&amp;&amp;(b%gcd==0))) //当满足同时整除a,b时,就是正确答案；换言之，当不满足时，才需要让循环继续寻找下去 //把“满足”的判断语句括起来，并在前面加上非运算符!，以表达“不满足”的判断语句 gcd=gcd-1; //如果当前gcd的值不是正确答案，就去尝试下一个可能的答案 printf(&quot;%d&quot;,gcd); //while循环会在满足条件时停止，此时gcd内的值即是正确答案 return 0; } 扩展阅读 对于最大公因数的计算，从数学原理上考虑，我们还有一种更为便捷的“辗转相除法”，如下所示： #include&lt;stdio.h&gt; int main() { int a, b, gcd; scanf(&quot;%d%d&quot;, &amp;a, &amp;b); int temp; if (a &lt; b) { temp = a; a = b; b = temp; } while (b != 0) { temp = a % b; a = b; b = temp; } printf(&quot;%d&quot;, a);//此时a是两者的最大公因数 return 0; } 一般来说，辗转相除法可以节约运行时间。在讲解“时间复杂度”的一章中，我们将能深刻体会到辗转相除法的优越性。 J 简单的一元三次方程 难度 考点 2 循环 题目分析 本题要求求解一个一元三次方程，不过对于答案做出了限制，为 −100-100−100 到 100100100 之间的整数。 我们仅需写一个 for 循环，枚举这些整数，看是否符合题意即可。 代码 #include&lt;stdio.h&gt; int main() { int a, b, c; scanf(&quot;%d%d%d&quot;, &amp;a, &amp;b, &amp;c); for (int i = -100; i &lt;= 100; i++) { if (i * i * i + a * i * i + b * i + c == 0) { printf(&quot;%d &quot;, i); } } return 0; } 注意事项 C语言中，^运算符表示按位异或，并非幂次。math.h 库中的 pow(a,b) 可以用来直接求 aba^bab ，其中 aaa，bbb 可以是浮点数。 K 好想变得和你一样强！ 难度 考点 3 思维 题目分析 从小到大给出 nnn 个数 a1,⋯ ,ana_1, \\cdots, a_na1​,⋯,an​ ，每次操作可以使一个数加 111 或者减 111 ，求最少多少次操作可以使所有数都变成相等的。 1≤n≤106, 0≤ai≤1091 \\leq n \\leq 10 ^ 6, \\ 0 \\leq a_i \\leq 10 ^ 91≤n≤106, 0≤ai​≤109 考虑这 nnn 个数最后都变成了什么，假设都变成了 xxx ，那么花费的操作次数即为： ∣a1−x∣+∣a2−x∣+∣a3−x∣+⋯+∣an−x∣|a_1-x| + |a_2-x| + |a_3-x| + \\cdots + |a_n-x| ∣a1​−x∣+∣a2​−x∣+∣a3​−x∣+⋯+∣an​−x∣ 考虑把绝对值转换成数轴上两点之间的距离。那么现在问题转换为：在一根数轴上，散落着 nnn 个点 a1,⋯ ,ana_1,\\cdots,a_na1​,⋯,an​ ，你需要找到一个点 xxx ，使得 xxx 到这 nnn 个点的距离之和最短。 我们试着将 xxx 在数轴上不停地移动，观察距离之和的变化。 假设现在 xxx 左边有 uuu 个点，右边有 vvv 个点。如果此时将 xxx 略微向左移动，那么就会有 uuu 个点的距离是减小的，vvv 个点的距离是增大的；反之，如果此时将 xxx 略微向右移动，那么就会有 uuu 个点的距离是增大的，vvv 个点的距离是减小的。 显然，如果左边的点比较多，我们就要将 xxx 往左移动；如果右边的点比较多，我们就要将 xxx 往右移动。 这样操作下来，不难发现，最优情况下，xxx 左边的点数和右边的点数必然一样多。否则，我们必然可以让 xxx 往点多的那一个方向移动，来使答案变得更优。 因此，若 nnn 为奇数，那么取 xxx 为 nnn 个数的中位数即可；若 nnn 为偶数，xxx 可以在 [an2,an2+1][a_{\\frac{n}{2}}, a_{\\frac{n}{2}+1}][a2n​​,a2n​+1​] 中任意取值，即在中间的两个数之间任意取值，为了方便，我们也可以直接取 x=an2x = a_{\\frac{n}{2}}x=a2n​​ 。 最后按照 xxx 的取值计算答案即可。 示例代码 #include &lt;stdio.h&gt; int main() { int T = 0; scanf(&quot;%d&quot;, &amp;T); for (int G = 1; G &lt;= T; G++) { int n = 0; scanf(&quot;%d&quot;, &amp;n); int x = 0; long long sum = 0; for (int i = 1; i &lt;= n / 2; i++) { scanf(&quot;%d&quot;, &amp;x); sum -= x; } scanf(&quot;%d&quot;, &amp;x); if (n % 2 == 0) sum += x; for (int i = 1; i &lt;= n - n / 2 - 1; i++) { scanf(&quot;%d&quot;, &amp;x); sum += x; } printf(&quot;%lld\\n&quot;, sum); } return 0; } ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"}],"title":"C1 - Solution-24航C","link":"https://github.pansis.site/post/C1 - Solution-24航C/","stats":{"text":"16 min read","time":910000,"words":3714,"minutes":16},"dateFormat":"2024-09-20"},{"content":"安装包地址 Dev-cpp是即装即用的C与C++的IDE，他自身带有C与C++的编译器。 安装包地址： https://bhpan.buaa.edu.cn/link/AA15DA77D790C24F7FAECA2311C791D444 有效期限：2027-10-08 18:26 下载好的安装包，按照安装程序提示安装。 Hello，world程序 点击 “文件-新建源代码”，得到一个新的源代码文件。 写一份hello，world的代码 点击编译运行 在保存为界面中选择自己存放代码的文件夹，同时保存类型务必选择c文件 保存后，程序完成编译并会运行。 出现上述界面就代表着你已经成功的写出了最经典的程序 hello，world。 调试 转载改编于Dev C++ 调试功能详细总结 - 知乎 (zhihu.com) **对于编程学习，可以说调试能力是最重要的技能，没有之一！**所谓调试程序，就是控制编译器一行一行地执行代码，过程中可以清楚看到每个变量值的变化情况、函数的调用过程等，进而发现程序中隐藏的错误或者低效的代码。 以下边的代码为例子: #include &lt;stdio.h&gt; int main() { int n, cnt = 0; scanf(&quot;%d&quot;, &amp;n); for(int j = 0; j &lt; n; j ++) { cnt = cnt + 1; } printf(&quot;%d&quot;, cnt); return 0; } 添加断点，这是调试功能的核心，下边是几个步骤： 1.左键添加断点，主要是在比较容易出错，或者有怀疑点的位置 2.添加变量查看，可以实时看到代码中变量的值，非常的直观 3.点击②后，会有③窗口，添加代码中变量名即可 4.输入回车后，在左侧④调试窗口就会出现变量的实时值 点击调试或者按F5启动调试功能，会出现黑色的控制台窗口 此时根据代码，代码会走到第六行scanf等待你的键盘输入 这里我们输入5回车 断点行会变为蓝色，意味着此时代码运行到第8行并停止 左侧变量n由于执存储了键盘的输入变为了5，cnt由于还没有执行仍为0 点击下一步，代码会继续执行一行，根据逻辑会运行到for循环入口，cnt由于执行了一次变为了1，而n由于没有操作所以还是5 另外几个调试功能的作用（都可以动手实践下）： 下一步：控制编译器向下执行一行语句； 单步进入：功能和“下一步”按钮类似，不同之处在于，当程序中调用某个自定义的函数时，此按钮可以进入到函数内部，继续调试函数内部的代码，而 “下一步” 按钮不会； 跳过：继续执行程序，遇到下一个断点暂停执行； 停止执行：停止调试程序。 当最后执行完for循环，printf函数会输出结果5到控制台，左侧cnt值也增长到了5 这就是一次相对比较简单的调试了，大家可以亲自动手实践下，可能会到各种奇怪的问题，都非常正常，开发本就是解决问题的一个过程，这个调试虽然简单，但是基本的原理就是这样了，对于很多项目中都可以用到。 ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"},{"index":-1,"name":"教程","slug":"GWAaV2nvk","used":true,"link":"https://github.pansis.site/tag/GWAaV2nvk/"}],"title":"Dev C++使用教程","link":"https://github.pansis.site/post/Dev C++使用教程/","stats":{"text":"4 min read","time":184000,"words":852,"minutes":4},"dateFormat":"2024-09-17"},{"content":"小熊猫C++ 小熊猫C++简介 小熊猫C++是基于Dev C++的二次开发版本。相比于Dev C++，增加了几项使用功能，包括但不限于代码提示、调试自动显示全部变量等功能。在本课程中，该IDE几乎可以做到与VS Code、Clion等现代IDE同等程度的便利（但大型项目上还得用VS Code、Clion等）。 安装包地址 小熊猫C++是即装即用的C与C++的IDE，他自身带有C与C++的编译器。 安装包地址：（仅含有Windows安装包，Mac用户可自行上官网下载） https://bhpan.buaa.edu.cn/link/AA81619A7482034198A42557A0ABE8E357 文件夹名：24航C 有效期限：永久有效 下载好的安装包，按照安装程序提示安装。 安装后，打开小熊猫C++，配置选项按下面的配置 Hello，world程序 点击 “文件-新建-新建C/C++文件”，得到一个新的源代码文件。 写一份hello，world的代码 点击运行按钮 在保存为界面中选择自己存放代码的文件夹，同时保存类型务必选择c文件 保存后，程序完成编译并会运行。 出现上述界面就代表着你已经成功的写出了最经典的程序 hello，world。 此文档为新手入门文档，如想继续深入进阶功能，请访问官方文档 概述 | 小熊猫C++ (royqh.net) 调试 **对于编程学习，可以说调试能力是最重要的技能，没有之一！**所谓调试程序，就是控制编译器一行一行地执行代码，过程中可以清楚看到每个变量值的变化情况、函数的调用过程等，进而发现程序中隐藏的错误或者低效的代码。 以下边的代码为例子: #include &lt;stdio.h&gt; int main() { int n, cnt = 0; scanf(&quot;%d&quot;, &amp;n); for(int j = 0; j &lt; n; j ++) { cnt = cnt + 1; } printf(&quot;%d&quot;, cnt); return 0; } 添加断点，这是调试功能的核心，下边是几个步骤： 1.左键添加断点，主要是在比较容易出错，或者有怀疑点的位置 2.启动调试 点击调试或者按F5启动调试功能，会出现黑色的控制台窗口 此时根据代码，你先输入一个数给变量n赋值，随后代码会走到第六行scanf等待你的键盘输入 这里我们输入5回车 断点行会变为蓝色，意味着此时代码运行到第8行并停止 右下角变量n由于存储了键盘的输入变为了5，cnt由于还没有执行仍为0 点击单步跨过，代码会继续执行一行，根据逻辑会运行到for循环入口，cnt由于执行了一次变为了1，而n由于没有操作所以还是5 另外几个调试功能的作用（都可以动手实践下）： 单步跨过、单步进入、单步跳出：控制编译器向下执行一行语句，但具体功能有所差异（调试中一般不适用，所以此处不细讲） 继续执行：继续执行程序，遇到下一个断点暂停执行；（最最最常用） 停止执行：停止调试程序。 当最后执行完for循环，printf函数会输出结果5到控制台，左侧cnt值也增长到了5 这就是一次相对比较简单的调试了，大家可以亲自动手实践下，可能会到各种奇怪的问题，都非常正常，开发本就是解决问题的一个过程，这个调试虽然简单，但是基本的原理就是这样了，对于很多项目中都可以用到。 ","tags":[{"index":-1,"name":"24航C","slug":"24hangc","used":true,"link":"https://github.pansis.site/tag/24hangc/"},{"index":-1,"name":"教程","slug":"GWAaV2nvk","used":true,"link":"https://github.pansis.site/tag/GWAaV2nvk/"}],"title":"小熊猫C++入门教程","link":"https://github.pansis.site/post/小熊猫C++入门教程/","stats":{"text":"4 min read","time":205000,"words":947,"minutes":4},"dateFormat":"2024-09-17"},{"content":"A A^T+A #include&lt;stdio.h&gt; int a[105][105]; int main() { int m; scanf(&quot;%d&quot;,&amp;m); for (int i = 0; i &lt; m; ++i) { for (int j = 0; j &lt; m; ++j) { scanf(&quot;%d&quot;,&amp;a[i][j]); } } for (int i = 0; i &lt; m; ++i) { for (int j = 0; j &lt; m; ++j) { printf(&quot;%lld &quot;,1ll*a[i][j]+a[j][i]); } printf(&quot;\\n&quot;); } } B See you again 关于输入 scanf(&quot;%s&quot;)和gets() 读入前 中止条件 读入后 scanf(&quot;%s&quot;) 会忽略\\n和空格 遇到空格或\\n 空格或\\n会留在缓冲区内 gets() 不会忽略\\n 遇到\\n 把\\n读入并转换为0作为字符串结束符 scanf(&quot;%c&quot;)与getchar() 在字符读入上完全等价，只是用法和返回值不同。 scanf(&quot;%c&quot;,&amp;a); a=getchar() %d等数字读入 %d、%lld、%lf等数字读入 读入前 中止条件 读入后 %d、%lld、%lf 忽略所有空白符 遇到非数字字符 不作处理 如果在读入前遇到了非数字字符，直接读入失败，不进行读入和赋值 何时需要使用getchar() 当先用%d读入一个数字，然后接着用gets()或getchar()、%c去读下一行的字符或字符串。 此时%d只会读数字，而\\n会留下来。 因此需要getchar()去把\\n吃掉，方便后续使用gets()或getchar()、%c去读下一行的字符或字符串。 实例 123 123 123 abcd cdef #include &lt;stdio.h&gt; int main() { int a,b,c,d; double e,f,g; char str1[100]; char str2[100]; char str3[100]; scanf(&quot;%d%d %d&quot;,&amp;a,&amp;b,&amp;c); // gets(str1); // gets(str2); // gets(str3); // return 0; scanf(&quot;%s&quot;,str1); gets(str2); gets(str3); return 0; } #include &lt;stdio.h&gt; #include &lt;string.h&gt; char s[1005]; int main() { int len;int a; gets(s); len=strlen(s); while ((a=getchar())!=EOF) { getchar(); int flag=0; for (int i = 0; i &lt;len ; ++i) { if (s[i]==a) { flag=1; printf(&quot;%d &quot;,i); } } if (flag==0) printf(&quot;-1&quot;); printf(&quot;\\n&quot;); } } C 小宇的圆周率2 示例代码1 #include&lt;stdio.h&gt; int main() { int n, m; char prt[100]; scanf(&quot;%d%d&quot;, &amp;n, &amp;m); //计算数列前 n 项和 int sign = 1; double sum = 0; for (int i = 0; i &lt; n; ++i) { sum += 4 * sign * 1.0 / (2 * i + 1); sign = -sign; } //构造输出格式 sprintf(prt, &quot;f(%d,%d) = %%.%df&quot;, n, m, m);//prt=&quot;f(100,2) = %.2f&quot;假设n=100，m=2 //输出结果 printf(prt, sum); return 0; } 示例代码2 #include&lt;stdio.h&gt; int main() { int n, m; char prt[100]; scanf(&quot;%d%d&quot;, &amp;n, &amp;m); //计算数列前 n 项和 int sign = 1; double sum = 0; for (int i = 0; i &lt; n; ++i) { sum += 4 * sign * 1.0 / (2 * i + 1); sign = -sign; } //构造输出格式 sprintf(prt, &quot;f(%%d,%%d) = %%.%df&quot;, m);// prt=&quot;f(%d,%d) = %.2f&quot;假设n=100，m=2 //输出结果 printf(prt, n, m, sum); return 0; } D 使一颗心免于哀伤 #include &lt;stdio.h&gt; #include &lt;string.h&gt; char str[1005]; char s[1005]; char nis[1005]; int main() { gets(str); gets(s); int s_len=strlen(s); int str_len=strlen(str); char *str_index=str; for (int i = 0; i &lt; s_len; ++i) { nis[i]=s[s_len-i-1]; } nis[s_len]='\\0'; char *index; while ((index=strstr(str_index,s))!=NULL) { for (int i = 0; i &lt; s_len; ++i) { *index=nis[i]; index++; } str_index+=s_len; } puts(str); } E 交换内存 malloc memcpy 指针类型 void Swap(void *p, void *q, size_t size){ void *temp= malloc(size); memccpy(temp,p,size); memccpy(p,q,size); memccpy(q,temp,size); free(temp); } // 请在下方补充Swap函数的定义。 F 象棋马走日 #include &lt;stdio.h&gt; #include &lt;string.h&gt; #include &lt;stdlib.h&gt; #include &lt;time.h&gt; int qipan[105][105]; int main() { int m,n,t; scanf(&quot;%d%d%d&quot;,&amp;m,&amp;n,&amp;t); int wz[8][2]={{-2,-1},{-2,1},{-1,-2},{-1,2},{1,-2},{1,2},{2,-1},{2,1}}; while (t--) { int x,y; scanf(&quot;%d%d&quot;,&amp;x,&amp;y); x--;y--; qipan[x][y]=-1; for (int i = 0; i &lt; 8; ++i) { if (x+wz[i][0]&gt;=0&amp;&amp;x+wz[i][0]&lt;m&amp;&amp;y+wz[i][1]&gt;=0&amp;&amp;y+wz[i][1]&lt;n) { if (qipan[x+wz[i][0]][y+wz[i][1]]!=-1) qipan[x+wz[i][0]][y+wz[i][1]]=1; } } } for (int i = 0; i &lt; m; ++i) { for (int j = 0; j &lt; n; ++j) { if (qipan[i][j]==1 || qipan[i][j]==-1) printf(&quot;0&quot;); else printf(&quot;1&quot;); } printf(&quot;\\n&quot;); } } ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"C6讲解-23航C","link":"https://github.pansis.site/post/c6-jiang-jie-23-hang-c/","stats":{"text":"6 min read","time":320000,"words":996,"minutes":6},"dateFormat":"2024-04-21"},{"content":"A A^T+A 难度 考点 2 二维数组 循环 题目分析 首先，使用一个二重循环将矩阵读入并写入二维数组 aaa 中。然后再用一个二重循环计算 AT+AA^T+AAT+A​ 各元素的值。 在矩阵 AAA 中，第 iii 行 jjj 列的元素是 a[i][j]，矩阵 ATA^TAT 中，第 iii 行 jjj 列的元素是 a[j][i]。 示例代码 #include&lt;stdio.h&gt; int a[105][105]; int main() { int m; scanf(&quot;%d&quot;,&amp;m); for (int i = 0; i &lt; m; ++i) { for (int j = 0; j &lt; m; ++j) { scanf(&quot;%d&quot;, &amp;a[i][j]); } } for (int i = 0; i &lt; m; ++i) { for (int j = 0; j &lt; m; ++j) { printf(&quot;%d &quot;, a[i][j] + a[j][i]); } printf(&quot;\\n&quot;); } } Author：pyhAuthor：pyhAuthor：pyh B See you again 难度 考点 2 指针，strchr函数 题目分析 本题考察 strchr 函数的用法，该函数的返回值指向该字符串中第一次出现所要查找的字符的指针，如果不存在则返回NULL 。因此我们可以利用该函数通过循环找出所有该字符存在的位置，将它与字符串的首指针相减即可得出该位置的下标，要注意处理每一行结束的换行符。 除此之外，也可以有不适用指针的做法，从前向后遍历字符串找到指定的字符即可。 示例代码 1 #include&lt;stdio.h&gt; #include&lt;string.h&gt; char s[1010], ch; int main() { scanf(&quot;%s&quot;, s); getchar(); // 读掉行末换行符 while(scanf(&quot;%c&quot;, &amp;ch) != EOF) { // 不定组输入 char *p = strchr(s, ch); if(p != NULL) { // 如果能够找到 while(p != NULL) { // 循环找出所有的 printf(&quot;%d &quot;, p - s); // p-s就是下标 p = strchr(p + 1, ch); // 从下一个位置开始找 } } else { // 找不到输出-1 printf(&quot;-1&quot;); } printf(&quot;\\n&quot;); getchar(); // 读掉行末换行符 } return 0; } 示例代码 2 #include&lt;stdio.h&gt; #include&lt;string.h&gt; char s[1010], ch; int main() { scanf(&quot;%s&quot;, s); getchar(); int l = strlen(s); // 记录字符串长度 while(scanf(&quot;%c&quot;, &amp;ch) != EOF) { int flag = 0; for(int i = 0; i &lt; l; i++) { // 用下标遍历整个字符串 if(s[i] == ch) { // 找到了 printf(&quot;%d &quot;, i); // 输出下标 flag = 1; } } if(!flag) { // 没找到 printf(&quot;-1&quot;); } printf(&quot;\\n&quot;); getchar(); } return 0; } C 小宇的圆周率2 难度 考点 2~3 sprintf 题目分析 这道题主要考察通过 sprintf 构造 printf 所需要的格式化字符串的知识点。 对于数列前 nnn 项和的计算，在 E2-小宇的圆周率 中已经讲解过，此处不再讲解。 当 n=100n=100n=100，m=2m=2m=2 我们通过 printf 输出的内容为 f(100,2) = 3.13，对应 printf 所需要构造的格式化字符串 sss 为 f(100,2) = %.2f。对应的要想构造字符串 sss，sprintf 所需要构造的格式化字符串 s′s&#x27;s′ 为 f(%d,%d) = %%.%df 。 或者，也可以采用示例代码2中的构造方式，两者的区别在于 f(n,m) 中 nnn 和 mmm 的引入位置上的区别。 再或者，我们也可以采用 Hint 所给的简便方法，无需 sprintf 去构造格式化字符串，而是直接采用 printf(&quot;f(%d,%d) = %.*f&quot;, n, m, m, pi); 输出（pi 为数列前 nnn 项和）。 示例代码1 #include&lt;stdio.h&gt; int main() { int n, m; char prt[100]; scanf(&quot;%d%d&quot;, &amp;n, &amp;m); // 计算数列前 n 项和 int sign = 1; double sum = 0; for (int i = 0; i &lt; n; ++i) { sum += 4 * sign * 1.0 / (2 * i + 1); sign = -sign; } // 构造输出格式 sprintf(prt, &quot;f(%d,%d) = %%.%df&quot;, n, m, m); // 若n=100, m=2，则prt为&quot;f(100,2) = %.2f&quot; // 输出结果 printf(prt, sum); return 0; } 示例代码2 #include&lt;stdio.h&gt; int main() { int n, m; char prt[100]; scanf(&quot;%d%d&quot;, &amp;n, &amp;m); // 计算数列前 n 项和 int sign = 1; double sum = 0; for (int i = 0; i &lt; n; ++i) { sum += 4 * sign * 1.0 / (2 * i + 1); sign = -sign; } // 构造输出格式 sprintf(prt, &quot;f(%%d,%%d) = %%.%df&quot;, m);// 若n=100, m=2，则prt为&quot;f(%d,%d) = %.2f&quot; // 输出结果 printf(prt, n, m, sum); return 0; } 示例代码3 #include&lt;stdio.h&gt; int main() { int n, m; scanf(&quot;%d%d&quot;, &amp;n, &amp;m); // 计算数列前 n 项和 int sign = 1; double sum = 0; for (int i = 0; i &lt; n; ++i) { sum += 4 * sign * 1.0 / (2 * i + 1); sign = -sign; } printf(&quot;f(%d,%d) = %.*f&quot;, n, m, m, sum); return 0; } Author：pyhAuthor：pyhAuthor：pyh D 使一颗心免于哀伤 难度 考点 3~4 字符串操作，strstr 题目分析 思路一：参考ppt上例7-9的示例代码，提前计算好逆序 sss，按照Hint实现即可。 思路二： 设需要查找的子串 sss 长度为 lenlenlen，使用指针 ppp 指向每次查找到的子串开头，将其之后的 lenlenlen 个字符逆序替换。下次查找从 ppp 之后 lenlenlen 的位置开始。 示例代码 1 思路一 #include &lt;stdio.h&gt; #include &lt;string.h&gt; char buf[1005], str[1005], rev[1005]; void print_str(const char *p1, const char *p2); // 输出区间[p1,p2)中的字符 char *str_rev(char *rev, const char *str); // 将str逆序存储在rev中，返回rev int main() { char *p1 = buf, *p2 = 0; fgets(buf, 1005, stdin); gets(str); str_rev(rev, str); // 计算s的逆序 while (p2 = strstr(p1, str)) { print_str(p1, p2); printf(rev); // 输出s的逆序 p1 = p2 + strlen(str); } fputs(p1, stdout); // 输出余下无匹配的子串 return 0; } void print_str(const char *p1, const char *p2) { while (p1 &lt; p2) { putchar(*p1); p1++; } } char *str_rev(char *rev, const char *str) { int len = strlen(str); for(int i = 0; i &lt; len; ++i) rev[i] = str[len - 1 - i]; rev[len] = '\\0'; return rev; } 示例代码 2 思路二 #include &lt;stdio.h&gt; #include &lt;string.h&gt; char str[1005]; char s[1005]; int main(){ gets(str); // 包含空格，使用gets输入包括空格的一整行 gets(s); char *loc = str; // 搜索子串的起始位置，第一步从字符串头开始 int len = (int)strlen(s); while (strstr(loc, s) != NULL) { // 查找不到新的串时跳出 char *p = strstr(loc, s); // 当前找到的字符串 for (int i = 0; i &lt; len; i++ { p[i] = s[len - 1 - i]; // 逆序替换 //也可以写成 *(p + i) = s[len - 1 - i]; } loc = p + len; // 更新下一次查找位置 } puts(str); // 输出 return 0; } Let my heart bravely spread the wings 🕊 Soaring past the night 🌃 To trace the bright moonlight 🌙 Let the clouds heal me of the stings ☁️ Gently wipe the sorrow of my life 💧 I dream 💭 Author: SiSi E 马走日 难度 考点 3~4 二维数组，边界判断 题目分析 一个点 (x,y)(x,y)(x,y) 上的马可以一步走到以下 888 个点上： (x+1,y+2),(x+1,y−2),(x−1,y+2),(x−1,y−2),(x+2,y+1),(x+2,y−1),(x−2,y+1),(x−2,y−1)(x+1,y+2),(x+1,y-2),(x-1,y+2),(x-1,y-2),(x+2,y+1),(x+2,y-1),(x-2,y+1),(x-2,y-1) (x+1,y+2),(x+1,y−2),(x−1,y+2),(x−1,y−2),(x+2,y+1),(x+2,y−1),(x−2,y+1),(x−2,y−1) 以上 888 个点上的马，也可以一步走到点 (x,y)(x,y)(x,y) 上。 因此我们可以设计两种思路。 用一个二维数组记录棋盘的情况，每读入一个马的坐标，将该点和这个马一步能走到的位置在二维数组中进行标记，最后输出二维数组的情况。参考示例代码 1 和示例代码 2 。 示例代码 1 #include &lt;stdio.h&gt; int a[110][110]; // 记录棋盘情况，初始均为0 // (dx[k],dy[k])表示马走一步到上述8个点中第k个点，横纵坐标的变化 int dx[8] = {1, 1, -1, -1, 2, 2, -2, -2}; int dy[8] = {2, -2, 2, -2, 1, -1, 1, -1}; int main() { int m, n, T; scanf(&quot;%d%d%d&quot;, &amp;m, &amp;n, &amp;T); while(T--) // 读入T个马 { int x, y; scanf(&quot;%d%d&quot;, &amp;x, &amp;y); a[x][y] = 1; // 该位置上有马，标记为1 for(int k = 0; k &lt; 8; ++k) // 遍历(x,y)上的马一步能走到的所有位置(i,j) { int i = x + dx[k], j = y + dy[k]; if(1 &lt;= i &amp;&amp; i &lt;= m &amp;&amp; 1 &lt;= j &amp;&amp; j &lt;= n) // 边界判断 a[i][j] = 1; // 该位置能被马一步走到，标记为1 } // 写8个if去枚举8个位置也可以，但比循环麻烦，且易错。 } // 输出棋盘上的每个位置 for(int i = 1; i &lt;= m; ++i) { for(int j = 1; j &lt;= n; ++j) printf(&quot;%d&quot;, a[i][j] ? 0 : 1); // 不满足要求的位置要输出0，满足的位置输出1 printf(&quot;\\n&quot;); } return 0; } 示例代码 2 与示例代码 1 基本相同，但是读入的坐标均+1，保证了不会越界访问，方便实现。这个方法简便，但可能并不通用。 #include &lt;stdio.h&gt; int a[110][110]; int dx[8] = {1, 1, -1, -1, 2, 2, -2, -2}; int dy[8] = {2, -2, 2, -2, 1, -1, 1, -1}; int main() { int m, n, T; scanf(&quot;%d%d%d&quot;, &amp;m, &amp;n, &amp;T); while(T--) { int x, y; scanf(&quot;%d%d&quot;, &amp;x, &amp;y); a[++x][++y] = 1; // x+=1, y+=1, a[x][y] = 1 for(int k = 0; k &lt; 8; ++k) // 遍历(x,y)上的马一步能走到的所有位置(x+dx,y+dy) a[x + dx[k]][y + dy[k]] = 1; } // 输出棋盘上的每个位置，由于下标都加1，因此下标从2开始遍历 for(int i = 2; i &lt;= m + 1; ++i) { for(int j = 2; j &lt;= n + 1; ++j) printf(&quot;%d&quot;, !a[i][j]); printf(&quot;\\n&quot;); } return 0; } F 交换内存 难度 考点 3~4 指针作为函数参数的设计，void* 类型指针，memcpy, malloc, free 函数的使用 题目分析 题目要求实现一个函数 void Swap(void *p, void *q, size_t size);，该函数的功能是：交换指针 p 指向的大小为 size 的内存和指针 q 指向的大小为 size 的内存。 参考Hint，有以下两种思路： 思路一 申请空间，对整块内存进行移动，实现交换。注意 free 语句不可缺少，申请空间要有借有还。 如果缺少 free 语句，可能会导致 MLE（超出内存限制）的评测结果。 void Swap(void *p, void *q, size_t size) { void *t = malloc(size); // 申请一块大小为size的内存空间，地址为t memcpy(t, p, size); // 将p指向的大小为size的内存复制给t memcpy(p, q, size); // 将q指向的大小为size的内存复制给p memcpy(q, t, size); // 将t指向的大小为size的内存复制给q free(t); // 释放t指向的内存空间 } 思路二 遍历内存的每个字节，将内容进行交换。利用 char 类型（占用空间大小为一字节）实现，涉及到指针的强制转换。 void Swap(void *p, void *q, size_t size) { for(int i = 0; i &lt; size; i++, p++, q++) // 循环size次，每次循环后移动p,q指向下一个字节的内存 { char t = *(char *)p; // 将p指向的一个字节的内存赋值给t *(char *)p = *(char *)q; // 将q指向的一个字节的内存赋值给p指向的一个字节的内存 *(char *)q = t; // 将t赋值给p指向的一个字节的内存 } } 提交的代码 将上方的内容补充在代码模板中并提交即可。 思路一 #include &lt;stdlib.h&gt; #include &lt;string.h&gt; void Swap(void *p, void *q, size_t size) { void *t = malloc(size); memcpy(t, p, size); memcpy(p, q, size); memcpy(q, t, size); free(t); } int main() { return 0; } 思路二 #include &lt;stdlib.h&gt; #include &lt;string.h&gt; void Swap(void *p, void *q, size_t size) { for(int i = 0; i &lt; size; i++, p++, q++) { char t = *(char *)p; *(char *)p = *(char *)q; *(char *)q = t; } } int main() { return 0; } G 通配符 难度 考点 4~5 字符串 题目分析 本题的关键在于实现自定义字符串比较函数。和普通的 strcmp 不同，这个函数需要忽略字符串中的 '*' 字符。于是，在判断子串和母串是否能匹配上时，为了忽略子串中的 '*' 字符，只需要在子串对应字符是 '*' 时直接认定该位字符两者相等即可。 需要注意的是，子串中的 '*' 不能和母串的空字符对应。对此，你可以在函数中进行限制，在读到母串的空字符时提前结束，或者你也可以在枚举可能匹配的位置时，提前把这种子串无法完全包含在母串的位置给排除掉。 示例代码根据指针所学知识来写，有许多指针操作。如果实在不会指针，那用下标来代替指针也不是不行…… 示例代码 1 #include &lt;stdio.h&gt; #include &lt;string.h&gt; int cmp(char *str, char *sub) // 功能为，从传入的str串的开头比较，看其能否与子串sub匹配 { char *p1 = str, *p2 = sub; while (*p2 != '\\0' &amp;&amp; *p1 != '\\0') { if (*p1 != *p2 &amp;&amp; *p2 != '*') { return 0; // 子串和母串对应位置字符不同，且子串此位置也不是通配符，则一定不能匹配。 } p1++, p2++; } if (*p2 == 0) // 判断子串是否各位都已判断 { return 1; } else { return 0; } } char str[50005], sub[105]; int pos[50005], cnt; int main(void) { int i, len1, len2, t; scanf(&quot;%d&quot;, &amp;t); while (t--) { scanf(&quot;%s%s&quot;, str, sub); len1 = strlen(str); len2 = strlen(sub); cnt = 0; // 答案数，记得每轮循环开始前提前归零 for (i = 0; i &lt;= len1 - len2; i++) // 提前约束可能匹配的范围 { if (cmp(str + i, sub)) // 此处传入的第一个参数的含义是，str串从第i位开始往后的部分 { pos[cnt++] = i + 1; // 把匹配成功的位置存入数组，稍后输出 } } printf(&quot;%d\\n&quot;, cnt); if (cnt != 0) { for (i = 0; i &lt; cnt; i++) { printf(&quot;%d &quot;, pos[i]); } printf(&quot;\\n&quot;); } } return 0; } 示例代码 2 /* Author: 助教头子 */ #include &lt;stdio.h&gt; #include &lt;string.h&gt; // match: 判断str能否与pattern匹配，若能匹配返回1，否则返回0 int match(const char *str, const char *pattern) { for(int i = 0; pattern[i]; i++) // pattern[i]不为'\\0'时，继续循环 if(str[i] == '\\0' || str[i] != pattern[i] &amp;&amp; pattern[i] != '*') return 0; // str[i]为'\\0'，或str[i]不能与pattern[i]匹配时，返回0 return 1; // 全都能匹配上，返回1 } // my_strstr：在str中从左到右寻找第一个能够与sub_str匹配的字符串 const char *my_strstr(const char *str, const char *sub_str) { for(const char *start = str; *start; start++) // 在str中遍历起始位置，与sub_str进行匹配 if(match(start, sub_str)) return start; // 匹配成功，返回匹配成功的位置 return NULL; // 没有匹配成功的位置，返回空指针 } char s1[50005], s2[55]; int pos[50005]; // 用于记录个数 int main() { int t; scanf(&quot;%d&quot;, &amp;t); while(t--) { scanf(&quot;%s%s&quot;, s1, s2); int num = 0; // 记录匹配成功的个数 for(const char *p = s1; p = my_strstr(p, s2); p++) // 与使用strstr函数类似，利用指针在s1中查找匹配s2的位置 pos[num++] = p - s1; // 将匹配成功的位置存下来。 printf(&quot;%d\\n&quot;, num); if(num) // 如果num不为0 { for(int i = 0; i &lt; num; ++i) // 输出所有匹配成功的位置 printf(&quot;%d &quot;, pos[i] + 1); // 注意次序等于下标+1 puts(&quot;&quot;); // 换行 } } return 0; } H 删除字母对 难度 考点 5 栈 题目分析 本题的第一反应可能是，不断遍历整个字符串，删除其中的字母对，直到某次遍历没有找到任何字母对为止。 但是注意字符串的长度，这么做的最坏情况是字符串的左边一半全是大写字母，右边一半全是小写字母，且左右都是匹配的，而你每次遍历整个字符串只能删除一对字母，显然时间复杂度达到了 O(n2)\\mathrm{O}(n^2)O(n2)，会导致超时。 这题的正确思路是使用栈的思想，本质上是一个有26种括号的括号匹配，当栈顶元素和传入元素能够匹配上时，就弹出栈顶，否则就把传入元素压栈。注意到字符串 sss 的第 iii 位与第 jjj 位会被一起删除，当且仅当 sis_isi​ 与 sjs_jsj​ 组成字母对，且 sis_isi​ 与 sjs_jsj​ 之间的全部元素都会被删除。因此遍历一次整个字符串之后，我们就已经删除完毕了全部可能存在的字母对，只要没删干净，即只要栈不为空，那么输出整个栈即可。时间复杂度为 O(n)\\mathrm{O}(n)O(n)。 示例代码 1 #include &lt;stdio.h&gt; #include &lt;string.h&gt; char stack[100005]; int top; // 记录当前栈里面有多少元素 char str[100005]; int main(void) { int i, len, t; scanf(&quot;%d&quot;, &amp;t); while (t--) { scanf(&quot;%s&quot;, str); len = strlen(str); for (i = 0; i &lt; len; i++) { if (top != 0 &amp;&amp; str[i] - stack[top - 1] == 'a' - 'A') { top--; // 弹栈操作 } else { stack[top] = str[i]; // 压栈操作 top++; } } if (top == 0) // 栈为空，说明字符串被完全删除 { printf(&quot;Done!\\n&quot;); } else { stack[top] = '\\0'; // 由于这个栈就类似一个字符串，把它变成一个合法字符串（末尾为'\\0'）后可直接用printf输出 printf(&quot;%s\\n&quot;, stack); } memset(stack, 0, sizeof(stack)); top = 0; // 记得清空整个栈 } return 0; } 示例代码 2 另一种实现，一边读入一边处理。 #include &lt;stdio.h&gt; char s[100005]; // 栈 int main() { int t; scanf(&quot;%d &quot;, &amp;t); // %d后面加空格，跳过换行符 while(t--) // 输入t行 { int top = -1; // 栈顶位置 while(1) { s[++top] = getchar(); // 读入一个字符 if(s[top] == EOF || s[top] == '\\n') // 读到文件结尾或者读完当前行，结束本行的判断 break; if(top &amp;&amp; s[top] - s[top - 1] == 'a' - 'A') // 如果不是第一个字符，且能与上一个字符形成字母对 top -= 2; // 删除 } s[top] = '\\0'; // 别忘了字符串的最后应为'\\0' if(top) puts(s); // 没删完 else puts(&quot;Done!&quot;); // 删完了 } return 0; } I Baymax 的破旧键盘 难度 考点 5~6 字符串，数组复杂应用 题目分析 本题最简单的想法就是用一个数组来保存输入的字符串，然后用一个变量 pos 保存「光标位置」。这样，输入一个字符相当于在数组中插入一个字符。 但很可惜，这样的代码会超时。因为每输入一个字符都可能会引起大量字符移动。在极端情况下，例如输入 250000025000002500000 个 a 和 [ 交替出现，则一共需要 0+1+2+⋯+2499999=6×10120+1+2+\\cdots+2499999=6\\times10^{12}0+1+2+⋯+2499999=6×1012​ 次字符移动。 这个问题该如何解决呢？我们可以创建一个 next 数组来记录输入字符的位置。设输入字符串为s[1~n] ，则可以用 next[i] 表示在当前显示屏中 s[i] 右边的字符位置。 为了方便起见，假设字符串 s 的最前面还有一个虚拟的 s[0]，则 next[0] 就可以表示显示屏中最左边的字符。再用一个变量 cur 表示光标位置：即当前光标位于 s[cur] 的右边。cur=0 说明光标位于 s[0] 的右边，即显示屏的最左边。为了移动光标，还需要一个 last 变量表示显示屏的最后一个字符是 s[last]。代码如下： 示例代码 #include &lt;stdio.h&gt; #include &lt;string.h&gt; #define maxn 100005 int next[maxn], last, cur; char s[maxn]; int main() { while (scanf(&quot;%s&quot;, s + 1) != EOF) { last = cur = 0; //初始化last和cur next[0] = 0; int len = strlen(s + 1); for (int i = 1; i &lt;= len; i++) { if (s[i] == '[') //将光标移动到最前端 cur = 0; else if (s[i] == ']') //将光标移动到最后端 cur = last; else { next[i] = next[cur]; //将字符接在当前光标的下一个位置 next[cur] = i; if (cur == last) //更新最后一个字符的编号 last = i; cur = i; //移动光标 } } for (int i = next[0]; i != 0; i = next[i]) putchar(s[i]); printf(&quot;\\n&quot;); } } 另一个思路 from cwz(aka.Gino) 本题有一个思路上更简单的办法。我们知道，[ 会把光标移动到字符串的头部，而且越后面出现的 [，其后跟随的字符串也会在越前面。] 的作用相当于复位，使得字符串重新从尾端开始延申。于是，我们首先可以从字符串最右边向左遍历，找到 '[' 字符，然后将其右边的一段字符串输出出来（以 '[', ']', '\\0' 作为终止标志）这样我们就把所有被插入左边的字符串给首先输出了。接下来，剩下的没有被输出的字符，它们都一定集中在右边，而且它们的相对位置一定不会改变，只需要再从左往右遍历一遍，顺序输出还没有输出过的字符即可。 示例代码 #include &lt;stdio.h&gt; #include &lt;string.h&gt; char s[100005]; int used[100005]; // 用于记录每个位置的字符是否被输出过 void print(int start) // 以[,],\\0 作为该段字符串结束的标志。 { int i; for (i = start; s[i] != ']' &amp;&amp; s[i] != '[' &amp;&amp; s[i] != 0; i++) { putchar(s[i]); used[i] = 1; } } int main(void) { int i; while (scanf(&quot;%s&quot;, s) != EOF) { int len = strlen(s); memset(used, 0, sizeof(used)); for (i = len - 1; i &gt;= 0; i--) // 从右到左遍历，输出所有被插到前面的字符串 { if (s[i] == '[') { print(i + 1); // 将'['右边的一段字符串输出 } } for (i = 0; i &lt; len; i++) // 从左到右遍历，输出那些还未输出的字符串 { if (!used[i] &amp;&amp; s[i] != ']' &amp;&amp; s[i] != '[') { putchar(s[i]); } } printf(&quot;\\n&quot;); } return 0; } J 回文游戏 难度 考点 6~7 区间dp 题目分析 设 dpi,jdp_{i,j}dpi,j​ 表示移除掉 [i,j][i,j][i,j] 中所有数所需要的最短时间，那么有转移方程 dpi,j={dpi+1,j−1, if ai=ajmin⁡i≤k&lt;j(dpi,k+dpk+1,j)dp_{i,j}=\\begin{cases} dp_{i+1,j-1} &amp; ,\\text{ if } a_i=a_j\\\\ \\underset{i\\leq k&lt;j}{\\min}(dp_{i,k}+dp_{k+1,j}) &amp; \\end{cases} dpi,j​=⎩⎨⎧​dpi+1,j−1​i≤k&lt;jmin​(dpi,k​+dpk+1,j​)​, if ai​=aj​​ 特别地，我们需要对区间长度小于 222 的单独初始化。注意在转移时即便两端点相等，依然有可能从中间断开更优。 时间复杂度 O(n3)O(n^3)O(n3) 。 示例代码 #include&lt;stdio.h&gt; #define min(a,b) ((a) &lt; (b) ? (a) : (b)) const int inf = 1e9; int c[510], dp[510][510]; int main() { int n; scanf(&quot;%d&quot;, &amp;n); for(int i = 1; i &lt;= n; i++) { for(int j = i; j &lt;= n; j++) { dp[i][j] = inf; } } for(int i = 1; i &lt;= n; i++) { scanf(&quot;%d&quot;, &amp;c[i]); dp[i][i] = 1; } for(int i = 1; i &lt;= n - 1; i++) { if(c[i] == c[i + 1]) { dp[i][i + 1] = 1; } else { dp[i][i + 1] = 2; } } for(int l = 2; l &lt;= n - 1; l++) { for(int i = 1; i + l &lt;= n; i++) { if(c[i] == c[i + l]) { dp[i][i + l] = dp[i + 1][i + l - 1]; } for(int j = 0; j &lt;= l - 1; j++) { dp[i][i + l] = min(dp[i][i + l], dp[i][i + j] + dp[i + j + 1][i + l]); } } } printf(&quot;%d&quot;, dp[1][n]); return 0; } - End - ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"C6 - Solution-23航C","link":"https://github.pansis.site/post/c6-solution-23-hang-c/","stats":{"text":"26 min read","time":1535000,"words":5631,"minutes":26},"dateFormat":"2024-04-21"},{"content":"A 每列几个空位？ 难度 考点 1 数组 题目分析 利用数组完成统计操作即可，详见示例代码 1 和 2 。 示例代码 1 利用二维数组横向地存储输入，再纵向地遍历统计。 #include&lt;stdio.h&gt; int a[505][505]; int main() { int m, n; scanf(&quot;%d%d&quot;, &amp;m, &amp;n); for (int i = 1; i &lt;= m; i++) { for (int j = 1; j &lt;= n; j++) { scanf(&quot;%1d&quot;, &amp;a[i][j]); } } for (int i = 1; i &lt;= n; i++) { int sum = 0; for (int j = 1; j &lt;= m; j++) { if (a[j][i] == 0) sum++; } printf(&quot;%d\\n&quot;, sum); } return 0; } 示例代码 2 用一个一维数组存储每一列的 0 的个数，若读到 0 ，对应的变量加一，最后再全部输出。 #include&lt;stdio.h&gt; int a; int sum[505]; int main() { int m, n; scanf(&quot;%d%d&quot;, &amp;m, &amp;n); for (int i = 1; i &lt;= m; i++) { for (int j = 1; j &lt;= n; j++) { scanf(&quot;%1d&quot;, &amp;a); if (a == 0) sum[j]++; } } for (int i = 1; i &lt;= n; i++) { printf(&quot;%d\\n&quot;, sum[i]); } return 0; } B 查询次序 难度 考点 2 冒泡排序，顺序查找 题目分析 先用冒泡排序对数组进行排序，然后顺序查找 aaa 即可。 冒泡排序： // 对数组a的前n个元素按照从小到大的顺序进行冒泡排序 void bubbleSort(int a[], int n) { for (int i = 1; i &lt; n; i++) { int flag = 0; for (int j = 0; j &lt; n - i; j++) if (a[j] &gt; a[j + 1]) { flag = 1; int hold = a[j]; a[j] = a[j + 1]; a[j + 1] = hold; } if(!flag) break; } } 顺序查找： // 在数组a的前n个元素中，找到key第一次出现的位置，返回下标；若key未出现，返回-1 int search(int a[], int n, int key) { for (int i = 0; i &lt; n; i++) if(key == a[i]) return i; return -1; } 示例代码 #include &lt;stdio.h&gt; void bubbleSort(int a[], int n); int search(int a[], int n, int key); int main() { int n, a, x[100]; scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) scanf(&quot;%d&quot;, &amp;x[i]); bubbleSort(x, n); while(~scanf(&quot;%d&quot;, &amp;a)) { int pos = search(x, n, a); // 顺序查找 if(pos != -1) printf(&quot;%d\\n&quot;, pos + 1); // 找到了，输出次序（等于下标+1） else puts(&quot;-1&quot;); // 没找到，输出-1 } return 0; } void bubbleSort(int a[], int n) { for (int i = 1; i &lt; n; i++) { int flag = 0; for (int j = 0; j &lt; n - i; j++) if (a[j] &gt; a[j + 1]) { flag = 1; int hold = a[j]; a[j] = a[j + 1]; a[j + 1] = hold; } if(!flag) break; } } int search(int a[], int n, int key) { for (int i = 0; i &lt; n; i++) if(key == a[i]) return i; return -1; } C 1-D Convolution 难度 考点 2 数组，结构化编程 题目分析 使用两个数组 f 和 g 分别存储输入序列和卷积核序列，再按要求计算输出即可。 注意输出序列的数最大值可达 104×104×104=101210^4 \\times 10^4 \\times 10^4 = 10^{12}104×104×104=1012 ，超出 int 类型的储存范围，故需使用 long long 类型进行计算。 注意求和变量的初始化和置零。 示例代码 #include &lt;stdio.h&gt; long long f[10005], g[10005]; int main() { int n, k; scanf(&quot;%d %d&quot;, &amp;n, &amp;k); for (int i = 0; i &lt; n; i++) // 输入序列 { scanf(&quot;%lld&quot;, &amp;f[i]); } for (int i = 0; i &lt; k; i++) // 输出序列 { scanf(&quot;%lld&quot;, &amp;g[i]); } for (int i = 0; i &lt; n - k + 1; i++) { long long sum = 0; // 每组求和初始化 for (int j = 0; j &lt; k; j++) { sum += f[i + j] * g[j]; } printf(&quot;%lld &quot;, sum); // 计算完可以直接输出，不参与后续计算，不需要使用数组存储 } return 0; } Author: SiSi D 系不系变量名 难度 考点 2 字符串 题意分析 使用语句 scanf(&quot;%s&quot;, s) 读入字符串，再判断是否为首字符，逐个判断满不满足变量名条件即可。 注意多组数据输入。 示例代码 1 #include &lt;stdio.h&gt; #include &lt;string.h&gt; #include &lt;ctype.h&gt; int main() { char s[101]; // 由于字符串在存储时末尾需要添加'\\0'，定义字符串长度需大于题目要求的长度。 while (scanf(&quot;%s&quot;, s) != EOF) { // 多组数据输入 int len = (int)strlen(s); int sign = 0; // 记录变量名是否合法 for (int i = 0; i &lt; len; i++) { if (i == 0) { // 首字符 if (!(s[i] == '_' || isalpha(s[i]))) { // 可以使用ctype.h中的函数简化判断过程 printf(&quot;1LLEGAL\\n&quot;); sign = 1; // 记录为不合法 break; } }else { // 其余字符 if (!(s[i] == '_' || isalnum(s[i]))) { printf(&quot;1LLEGAL\\n&quot;); sign = 1; break; } } } if (sign == 0) { // 未记录到不合法时即为合法 printf(&quot;LEGAL\\n&quot;); } } return 0; } 示例代码 2 #include &lt;stdio.h&gt; #include &lt;ctype.h&gt; int main() { char c; while(~(c = getchar())) // 读入每行第一个字符 { int flag = c == '_' || isalpha(c); // 判断第一个字符是否合法（是下划线或字母则合法） while(~(c = getchar()) &amp;&amp; c != '\\n') // 读完该行 if(c != '_' &amp;&amp; !isalnum(c)) flag = 0; // 判断后面的字符，不是下划线也不是字母或数字，则不合法 puts(flag ? &quot;LEGAL&quot; : &quot;1LLEGAL&quot;); // 输出该行的判断结果 } return 0; } Author: Simon E 秋月学线代 难度 考点 3 二维数组 题目解析 本题在思维上没有什么难度，只要模拟出三种矩阵的行变换就好，主要是考察大家的循环结构基本功，以及结构化编程的能力。 示例代码 #include &lt;stdio.h&gt; #define MAXN 5+100 int n, m; void matScan(int a[][MAXN]); // 输入矩阵 void matPrint(int a[][MAXN]); // 输出矩阵 void matLineSwap(int a[][MAXN], int x, int y); // 交换x和y两行 void matLineMul(int a[][MAXN], int x, int k); // 将第x行乘k void matLineAdd(int a[][MAXN], int x, int y, int k); // 将第y行加上第x行乘k int main() { int a[MAXN][MAXN]; scanf(&quot;%d%d&quot;,&amp;n,&amp;m); matScan(a); int op; while (scanf(&quot;%d&quot;, &amp;op) != EOF) { int i, j; int k; switch (op) { case 1: scanf(&quot;%d%d&quot;, &amp;i, &amp;j); matLineSwap(a, i, j); break; case 2: scanf(&quot;%d%d&quot;, &amp;i, &amp;k); matLineMul(a, i, k); break; case 3: scanf(&quot;%d%d%d&quot;, &amp;i, &amp;j, &amp;k); matLineAdd(a, i, j, k); break; default: break; } // matPrint(a); 加上这一行则可以输出变换过程 } matPrint(a); return 0; } void matScan(int a[][MAXN]) { for (int i = 1; i &lt;= n; ++i) { for (int j = 1; j &lt;= m; ++j) { scanf(&quot;%d&quot;, &amp;a[i][j]); } } } void matLineSwap(int a[][MAXN], int x, int y) { for (int i = 1; i &lt;= m; ++i) { int temp; temp = a[x][i]; a[x][i] = a[y][i]; a[y][i] = temp; } return; } void matLineMul(int a[][MAXN], int x, int k) { for (int i = 1; i &lt;= m; ++i) { a[x][i] *= k; } return; } void matLineAdd(int a[][MAXN], int x, int y, int k) { for (int i = 1; i &lt;= m; ++i) { a[y][i] += a[x][i]*k; } return ; } void matPrint(int a[][MAXN]) { for (int i = 1; i &lt;= n; ++i) { for (int j = 1; j &lt;= m; ++j) { printf(&quot;%d &quot;, a[i][j]); } puts(&quot;&quot;); } } F czx 的老鼠防治 难度 知识点 3~4 二维数组 题目分析 不妨用一个二维数组 a[305][305] 来模拟当前的网格图，按照输入顺序逐个输入老鼠的坐标 (x,y)(x, y)(x,y)，并在网格图中将 a[x][y] 置 1。表示这个格子已经有老鼠了。再考虑每次放入一个老鼠，需要 增加 多少个防鼠板。如果这个老鼠的上下左右四个格子，当前没有任何老鼠，那么需要 444 个防鼠板。如果这个老鼠的上下左右四个格子，每有一个老鼠，说明那个位置当前已有防鼠板，可以减少 111 个。 示例代码 #include &lt;stdio.h&gt; int n, m, k, a[305][305], res; int main() { scanf(&quot;%d %d %d&quot;, &amp;n, &amp;m, &amp;k); for (int i = 1, x, y; i &lt;= k; i++) { scanf(&quot;%d%d&quot;, &amp;x, &amp;y); a[x][y] = 1; res += 4 - a[x + 1][y] - a[x][y + 1] - a[x][y - 1] - a[x - 1][y]; } printf(&quot;%d\\n&quot;, res); return 0; } G 这是回文串吗？ 难度 考点 4 二维数组，字符串 题目分析 本题需要用到二维字符数组。特别需要注意的是按列读取字符串时需要忽略空字符 '\\0'。对此，我们可以设置两个下标 l, r 用于检查字符串最后和最前的字符是否一致。为了能检查到整个一列，l 的初始值显然应当设为 000，而 r 的初始值虽然无法用 strlen 这种以 '\\0' 作为结束标志的函数来获取，但是我们可以直接设置其为行数，也就是一列的长度的最大值。如果 l 或 r 对应的字符为空字符，那么让其忽略这个字符。 值得注意的是，本方法需要对二维字符数组进行初始化，要么将其作为全局变量，要么可以在 main 函数里写成这样的形式：char s[105][105] = {}，否则数组中会存在大量随机值而非空字符 '\\0'，这虽然对于正常的按行读取没有任何问题，但对于本题的按列读取就会造成 l, r 检查的数组元素不可控。 示例代码 1 #include &lt;stdio.h&gt; char s[105][105]; int judge(int i) // 判断第i列是不是回文串 { int l = 0, r = 100; while (l &lt; r) { if (s[l][i] &amp;&amp; s[r][i]) // 只有l和r对应元素不是空字符时才进行回文串的判断。 { if (s[l][i] != s[r][i]) { return 0; // 判断出该列不可能是回文串了 } l++, r--; } while (!s[l][i] &amp;&amp; l &lt; r) { l++; // 让l指向其后面最近一个非空字符 } while (!s[r][i] &amp;&amp; l &lt; r) { r--; // 与上一个循环作用类似 } } return 1; // 没有找到该列不回文的地方，说明该列为回文串。 } int main(void) { int i, n; scanf(&quot;%d&quot;, &amp;n); for (i = 0; i &lt; n; i++) { scanf(&quot;%s&quot;, s[i]); } for (i = 0; i &lt; n; i++) { if (judge(i)) { printf(&quot;YES\\n&quot;); } else { printf(&quot;NO\\n&quot;); } } return 0; } 示例代码 2 #include &lt;stdio.h&gt; int n; char s[128][128]; int solve(int i) // 判断第i列是不是回文串 { int l = 0, r = n - 1; while(l &lt; r) { while(l &lt; n &amp;&amp;!s[l][i]) l++; // 向右找到第一个非空字符 while(r &gt;= 0 &amp;&amp; !s[r][i]) r--; // 向左找到第一个非空字符 if(l &gt;= r) break; // 包含了0&lt;=r&lt;=l&lt;n的情况、l&gt;=n的情况和r&lt;0的情况，均无需继续判断 if(s[l++][i] != s[r--][i]) return 0; // 若不同，不是回文串，直接返回0 } return 1; } int main() { scanf(&quot;%d%*c&quot;, &amp;n); for(int i = 0; i &lt; n; ++i) gets(s[i]); for(int i = 0; i &lt; n; ++i) puts(solve(i) ? &quot;YES&quot; : &quot;NO&quot;); return 0; } H姜姜切绳子 难度 考点 4~5 二分答案 题目分析 如果能确定切割绳子的长度，就能在 O(n)O(n)O(n) 下得到能切出多少段绳子，并且切割绳子的长度越大，得到的绳子段数越少，具有单调性，因此可以考虑二分答案。 对最大长度进行二分，然后检查该长度下是否能切出K段绳子，如果可以就在右区间继续二分，否则就在左区间二分。最后时间复杂度为 O(nlog⁡L)O(n\\log L)O(nlogL) 。 示例代码 #include&lt;stdio.h&gt; int n, k; int L[10005]; int check(int max_len) { int cnt = 0; for(int i = 1; i &lt;= n; i++) cnt += L[i] / max_len; return cnt &gt;= k; } // 函数功能：在区间[l,r]中找到最大的满足check(x)为真的x，并返回x int find_max(int l, int r) { while(l &lt; r) { int mid = (l + r + 1) / 2; // 此处+1是实现向上取整，为了防止死循环，并且能使得最后得到的l一定是正确答案 if(check(mid)) l = mid; else r = mid - 1; } return l; } int main() { scanf(&quot;%d%d&quot;, &amp;n, &amp;k); for(int i = 1; i &lt;= n; i++) scanf(&quot;%d&quot;, &amp;L[i]); int ans = find_max(1, 1e9); // 计算答案 printf(&quot;%d&quot;, ans); return 0; } I Gino 的酒局之战 难度 考点 5~6 动态规划 题目分析 如果采用暴力模拟，最终时间复杂度将达到 O(n2)\\mathrm{O}(n^2)O(n2)，显然不适合本题 n≤106n\\le 10^6n≤106 的数据范围。 因为队伍首尾相接，其过程涉及取模的运算，为了方便，这里暂时将 nnn 人编号为 0∼n−10\\sim n-10∼n−1。 首先把 aaa 的值抛开，思考，如果从第一个人开始数，那么最后剩下的编号是几呢？ 从最基本的 n=1n=1n=1 的情况开始思考，显然最后剩下的编号是 000，即 dp1=0dp_1=0dp1​=0。 接下来考虑到 n=2n=2n=2 的情况。由于步长的值是不断改变的，因此设本轮循环的步长为 step2step_2step2​，即每数 step2step_2step2​ 个人就退出一人。从编号为 000 开始数，编号为 (step2−1) mod 2(step_2-1)\\bmod 2(step2​−1)mod2 的人退出，编号为 step2 mod 2step_2 \\bmod 2step2​mod2 的人会作为下一轮循环开始的编号为 000 的人。已知在下一轮里，安全的编号是 dp1dp_1dp1​，那么可以得出，在本轮，安全的编号是 $dp_2=(dp_1+step_2)\\bmod 2 $。 推广到更一般的 n=in=in=i 的情况，我们可以得到，dpi=(dpi−1+stepi) mod idp_i=(dp_{i-1}+step_i)\\bmod idpi​=(dpi−1​+stepi​)modi 现在距离解出最后结果只差 stepistep_istepi​ 的表达式，而这就更简单了。步长是 mmm 到 111 的一个循环，这就又可以通过取模得到。由步长的初始值为 mmm，人数初始值为 nnn 可以得出，当前人数为 iii 时，stepi=m−(n−i) mod mstep_i=m-(n - i)\\bmod mstepi​=m−(n−i)modm。结合以上结论，最终的递推表达式为 dpi=(dpi−1+m−(n−i) mod m) mod idp_i=(dp_{i-1}+m-(n-i)\\bmod m)\\bmod idpi​=(dpi−1​+m−(n−i)modm)modi 接下来我们重新考虑起始位置的问题。不难发现对于固定的人数 nnn，安全位置与起始位置之间的间隔是不会变的。若起始位置由 000 变为 aaa，那么安全位置就会由原来的 dpdpdp 变为 (dp+a) mod n(dp+a)\\bmod n(dp+a)modn。注意仅需在递推完毕之后进行这一步计算即可。 最后，题目中给出的编号是从 1∼n1\\sim n1∼n 而非 0∼n−10\\sim n-10∼n−1，因此注意两种编号之间的转换。 综合以上过程，本题时间复杂度为 O(n)\\mathrm{O}(n)O(n)。 示例代码 #include &lt;stdio.h&gt; int dp[1000005]; int main(void) { int n, a, m, i; scanf(&quot;%d%d%d&quot;, &amp;n, &amp;a, &amp;m); dp[1] = 0; // 把编号当作从0~n-1 for (i = 2; i &lt;= n; i++) { dp[i] = (dp[i - 1] + m - (n - i) % m) % i; } dp[n] = (dp[n] + a - 1) % n; // 最后再根据起始位置算出最终答案 printf(&quot;%d&quot;, dp[n] + 1); // 输出时记得转化回原本的从1~n的编码 return 0; } J 村庄与供水站 难度 考点 6 动态规划 题目分析 ​ 本题解中使用的所有除号 /// 均表示整除，如 5/2=25/2=25/2=2 . ​ 修建 111 个供水站给 nnn 个村庄供水，应将供水站修在第 (n+1)/2(n+1)/2(n+1)/2 个村庄处，使得村庄到供水站距离之和最短，Hint 中有提示这一点。 ​ 因此，若修建 111 个供水站给从第 aaa 到第 bbb 个村庄供水 (a≤b)(a\\le b)(a≤b) ，使得从第 aaa 到第 bbb 个村庄与该供水站的距离之和最短，则供水站应修在第 (a+b)/2(a+b)/2(a+b)/2 个村庄处，设此最短距离和为 d(a,b)d(a,b)d(a,b) 。 ​ 若修建 iii 个供水站给前 jjj 个村庄供水 (i≤j)(i\\le j)(i≤j) ，使得前 jjj 个村庄到最近的供水站的距离之和最短，设此最短的距离之和为 dp(i,j)dp(i, j)dp(i,j) 。 ​ 找 i−1i-1i−1 个供水站与 iii 个供水站之间的递推关系： iii 个供水站给前 jjj 个村庄供水，可视为前 i−1i-1i−1 个供水站给前 kkk 个村庄供水，第 iii 个供水站给从第 k+1k+1k+1 到第 jjj 个村庄供水 (i−1≤k&lt;j)(i-1\\le k&lt;j)(i−1≤k&lt;j) 。因此有 dp(i,j)=dp(i−1,k)+d(k+1,i), i−1≤k&lt;jdp(i,j)=dp(i-1, k)+d(k+1, i),\\ i-1\\le k&lt;jdp(i,j)=dp(i−1,k)+d(k+1,i), i−1≤k&lt;j ，遍历 kkk 找到最小值，即可得到 dp(i,j)dp(i,j)dp(i,j) ，即： dp(i,j)={d(1,j),i=1min{dp(i−1,k)+d(k+1,i)∣ i−1≤k&lt;j},i&gt;1dp(i,j)=\\left\\{\\begin{matrix} d(1, j) &amp; , i=1\\\\ min\\left \\{ dp(i-1, k)+d(k+1, i)|\\ i-1\\le k&lt;j\\right \\} &amp;, i&gt;1 \\end{matrix}\\right. dp(i,j)={d(1,j)min{dp(i−1,k)+d(k+1,i)∣ i−1≤k&lt;j}​,i=1,i&gt;1​ ​ 将所有 d(a,b)d(a,b)d(a,b) 事先计算出来，用数组存储，在计算 dp(i,j)dp(i,j)dp(i,j) 时可直接使用。计算 d(a,b)d(a,b)d(a,b) 可以直接累加每个村庄到第 (a+b)/2(a+b)/2(a+b)/2 个村庄的距离，即 d(a,b)=∑i=ab∣xi−x(a+b)/2∣d(a,b)=\\sum_{i = a}^{b} |x_i-x_{(a+b)/2}|d(a,b)=∑i=ab​∣xi​−x(a+b)/2​∣ ，共三层循环嵌套计算；也可以由公式 d(a,b)=∑i=a(a+b)/2(xa+b−i−xi)d(a,b)=\\sum_{i=a}^{(a+b)/2}(x_{a+b-i}-x_i)d(a,b)=∑i=a(a+b)/2​(xa+b−i​−xi​) 导出递推式 d(a,b)=d(a,b−1)+xb−x(a+b)/2d(a,b)=d(a,b-1)+x_b- x_{(a+b)/2}d(a,b)=d(a,b−1)+xb​−x(a+b)/2​ ，用两层循环嵌套来计算。后者时间复杂度更低，但不影响总时间复杂度。 ​ 按此思路，遍历 i,j,ki, j, ki,j,k ，共三层循环嵌套，可以求得 dp(m,n)dp(m, n)dp(m,n) 。注意在最初要特别判断一下 n&lt;mn&lt;mn&lt;m 的情况，此时应该输出 000 。 ​ 总时间复杂度为 O(n3)O(n^3)O(n3) 。 示例代码 #include &lt;stdio.h&gt; #define min(a,b) ((a)&lt;(b)?(a):(b)) int main() { int n, m; int x[201]; int d[201][201]; // d[i][j]表示1个供水站给从第i到第j个村庄供水的最短的距离之和 int dp[201][201]; // dp[i][j]表示i个供水站给前j个村庄供水的最短的距离之和 scanf(&quot;%d%d&quot;, &amp;n, &amp;m); for(int i = 1; i &lt;= n; ++i) scanf(&quot;%d&quot;, &amp;x[i]); if(n &lt;= m) { printf(&quot;0&quot;); return 0; } for(int i = 1; i &lt;= n; ++i) { d[i][i] = 0; for(int j = i + 1; j &lt;= n; ++j) d[i][j] = d[i][j - 1] + x[j] - x[(i + j) / 2]; } /* for(int i = 1; i &lt;= n; ++i) { for(int j = i; j &lt;= n; ++j) { d[i][j] = 0; for(int k = i; k &lt; (i + j) / 2; ++k) d[i][j] += x[i + j - k] - x[k]; } } */ for(int i = 1; i &lt;= m; ++i) { for(int j = i; j &lt;= n; ++j) { dp[i][j] = d[i][j]; //i=1与i&gt;1,k=i-1的情况可以统一起来 if(i == 1) continue; for(int k = i; k &lt; j; ++k) dp[i][j] = min(dp[i - 1][k] + d[k + 1][j], dp[i][j]); } } printf(&quot;%d&quot;, dp[m][n]); return 0; } - End - ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"E5 - Solution-23航C","link":"https://github.pansis.site/post/E5 - Solution-23航C/","stats":{"text":"23 min read","time":1352000,"words":4754,"minutes":23},"dateFormat":"2024-04-21"},{"content":"A 单位向量 难度 考点 1 数组 题目分析 使用数组存储向量值，使用统计变量计算平方和，再按要求计算并输出即可。 示例代码 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main() { int n; scanf(&quot;%d&quot;, &amp;n); int a[105]; int sum = 0; // 平方和 for (int i = 0; i &lt; n; i++) { scanf(&quot;%d&quot;, &amp;a[i]); sum += a[i] * a[i]; // 累加 } for (int i = 0; i &lt; n; i++) { printf(&quot;%.3lf &quot;, a[i] / sqrt(sum)); // sqrt返回值为double，不需要再额外写类型转换操作 } return 0; } Author: SiSi B 小宇的成绩排序 难度 考点 2 冒泡排序 题目分析 这道题主要考察冒泡排序。先将乱序的程序读入数组后，利用冒泡排序升序排列后输出即可。 这题建议大家将冒泡排序的模板以函数的形式保存下来，需要用的时候可以直接复制粘贴，节省时间。 另外，这道题限制了排序的数值范围且范围较小，也可以采用简化的桶排序实现排序。创建一个元素个数为 100100100 的数组 aaa ，a[i] 的值等于 i 出现的次数，最后按照 i 从小到大的顺序，输出 a[i] 次 i 。 Tips：桶排序只能用于排序的数值范围很小的情况。 示例代码 1 - 冒泡排序 #include &lt;stdio.h&gt; void bubble_sort(int a[], int n) //升序冒泡排列 { int temp, flag; for (int i = 0; i &lt; n - 1; ++i) { flag = 0; for (int j = 0; j &lt; n - 1 - i; ++j) { if (a[j] &gt; a[j + 1]) { //相邻非升序需交换 temp = a[j + 1]; flag = 1; a[j + 1] = a[j]; a[j] = temp; } } if (flag == 0) break; //不存在交换情况时结束循环 } } int main() { int n; int a[10002]; scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; ++i) { //读入成绩 scanf(&quot;%d&quot;, &amp;a[i]); } bubble_sort(a, n); //冒泡排列 for (int i = 0; i &lt; n; ++i) { //输出成绩 printf(&quot;%d &quot;, a[i]); } return 0; } 示例代码 2 - 桶排序 #include &lt;stdio.h&gt; int a[101]; int main() { int n, t; scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { //读入成绩 scanf(&quot;%d&quot;, &amp;t); a[t]++; } for (int i = 0; i &lt;= 100; i++) while (a[i]--) //输出a[i]次i printf(&quot;%d &quot;, i); return 0; } Author：pyhAuthor：pyhAuthor：pyh C 字符串汉明距离 难度 考点 2 一维数组，数组名作为函数参数 题目分析 Hint已经足够详细，只需完善函数 HammingDistance 即可，如下： // 计算长度为len的两个字符串a和b之间的汉明距离 int HammingDistance(char a[], char b[], int len) { int res = 0; for(int i = 0; i &lt; len; ++i) if(a[i] != b[i]) res++; // 统计对应位置不相同的字符个数 return res; } 示例代码 #include &lt;stdio.h&gt; int HammingDistance(char a[], char b[], int len); // 计算长度为len的两个字符串a和b之间的汉明距离 char s[1005][1005]; // 字符型二维数组，用于存储多个字符串，每行存储一个字符串 int main() { int len, n, m; // 字符串长度，字符串个数，询问次数 scanf(&quot;%d%d%d&quot;, &amp;len, &amp;n, &amp;m); for(int i = 1; i &lt;= n; i++) // 读入n个字符串 scanf(&quot;%s&quot;, s[i]); // 这里s[i]前面无需加&amp; while(m--) // m次查询 { int i, j; scanf(&quot;%d%d&quot;, &amp;i, &amp;j); printf(&quot;%d\\n&quot;, HammingDistance(s[i], s[j], len)); // 输出s[i]和s[j]之间的汉明距离 // 注意这里的参数传递，将二维数组中的一行作为参数传入，即传入的是一维字符数组 } return 0; } int HammingDistance(char a[], char b[], int len) { int res = 0; for(int i = 0; i &lt; len; ++i) res += a[i] != b[i]; return res; } D 代码学园的挑战 难度 知识点 3 二分查找 题目分析 题目要求我们编写一个程序，实现按照学号从小到大的顺序排列的学生名单中查找指定学号学生的姓名。如果学号不存在，则输出 Not find!。题目中明确了学生名单是已经排序好的，因此我们可以采用二分查找算法来提高查找效率。 二分查找（Binary Search）是一种在 有序 数组中查找特定元素的搜索算法。它的基本思想是：在有序数组中，取中间数与所需查找的数进行比较，如果中间数小于所查找的数，则在数组大于中间数的部分继续查找，否则在数组小于中间数的部分继续查找，直到找到要查找的数字。二分查找的时间复杂度为 O(log⁡n)O(\\log n)O(logn)，是一种效率较高的查找方法。 对于这道题目，还有一个关键点是学生姓名（字符串）的储存。对于 char 类型的二维数组，一种简单粗暴的方式是将其理解为一个字符串的一维数组，然后对于每一个 s[i]，将它当作字符串来处理即可。 示例代码 1 - 循环实现 #include &lt;stdio.h&gt; #include &lt;string.h&gt; #define MAX_STUDENTS 100005 #define MAX_NAME_LENGTH 21 // 在长度为n的严格递增数组ids中查找x，如果找到则返回下标，否则返回-1 int binarySearch(int ids[], int n, int x) { int low = 0, high = n - 1; while (low &lt;= high) { int mid = (low + high) / 2; if (ids[mid] == x) { return mid; } else if (ids[mid] &lt; x) { // 如果ids是严格递减数组，将此处改为&gt;即可 low = mid + 1; } else { high = mid - 1; } } return -1; } int n, m; int ids[MAX_STUDENTS]; char names[MAX_STUDENTS][MAX_NAME_LENGTH]; int main() { scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) { scanf(&quot;%d%s&quot;, &amp;ids[i], names[i]); } scanf(&quot;%d&quot;, &amp;m); for (int i = 0; i &lt; m; i++) { int id; scanf(&quot;%d&quot;, &amp;id); int index = binarySearch(ids, names, n, id); // 二分查找 if (index != -1) { // 找到了 printf(&quot;%s\\n&quot;, names[index]); } else { // 没找到 printf(&quot;Not find!\\n&quot;); } } return 0; } 示例代码 2 - 递归实现 #include &lt;stdio.h&gt; int id[100005]; char name[100005][25] = {&quot;Not find!&quot;}; // name[0]初始化为查找不成功时的输出内容，将查找成功和未成功两种情况统一起来 int f(int l, int r, int key) // 在id数组的区间[l,r]中查找key，找到则返回下表，否则返回0 { if(l &gt; r) return 0; // l&lt;r，没找到 int mid = (l + r) / 2; if(id[mid] &gt; key) return f(l, mid - 1, key); // 缩小区间范围为[l,mid-1] if(id[mid] &lt; key) return f(mid + 1, r, key); // 缩小区间范围为[mid+1, r] return mid; // 此时一定有id[mid]==key，找到了，返回下标mid } int main() { int n, m; scanf(&quot;%d&quot;, &amp;n); for(int i = 1; i &lt;= n; ++i) scanf(&quot;%d%s&quot;, &amp;id[i], name[i]); scanf(&quot;%d&quot;, &amp;m); while(m--) // m次查找 { int t; scanf(&quot;%d&quot;, &amp;t); puts(name[f(1, n, t)]); // f(1,n,t)在id[1]到id[n]中查找t，返回下标i，输出name[i] } return 0; } E 误差函数 难度 考点 3 二分法解方程 题目分析 参考Hint和ppt中关于二分法解方程的内容和代码。 利用循环实现的核心代码如下： // 对于单调递增函数f(x)，已知f(x)=y的解x在区间[l,r]上，求解x： while(r - l &gt; eps) { // eps为自定义的小量，本题建议取值为1e-8 double mid = (l + r) / 2; if(f(mid) &gt; y) { // 若f(x)为单减函数，则此处改为f(mid)&lt;y即可 r = mid; } else { l = mid; } } // 此时l,r之间的差小于eps，在一定精度要求下可视为相等，均为方程f(x)=y的解 利用递归实现的核心代码如下： // 对于单调递增函数f(x)，已知f(x)=y的解x在区间[l,r]上，求解x： double solve(double l, double r, double y) { if(r - l &lt; eps) return l; double mid = (l + r) / 2; if(f(mid) &gt; y) return solve(l, mid, y); // 若f(x)为单减函数，则此处改为f(mid)&lt;y即可 else return solve(mid, r, y); } 示例代码 1 - 循环实现 #include &lt;stdio.h&gt; #include &lt;math.h&gt; #define eps 1e-10 double f(double x) { return erf(x / sqrt(2)); } double solve(double y) // 计算并返回关于x的方程f(x)=y的解（条件：f(x)为单调函数，已知解在区间[l,r]范围内） { double l = 0, r = 3; while(r - l &gt; eps) { double mid = (l + r) / 2; if(f(mid) &gt; y) r = mid; else l = mid; } // 此时l,r之间的差小于eps，在一定精度要求下可视为相等，均为方程f(x)=y的解 return l; } int main() { double p; while(~scanf(&quot;%lf&quot;, &amp;p)) printf(&quot;%.3f\\n&quot;, solve(p)); return 0; } 示例代码 2 - 递归实现 #include &lt;stdio.h&gt; #include &lt;math.h&gt; #define eps 1e-10 double f(double x) { return erf(x / sqrt(2)); } double solve(double l, double r, double y) { if(r - l &lt; eps) return l; double mid = (l + r) / 2; if(f(mid) &gt; y) return solve(l, mid, y); else return solve(mid, r, y); } int main() { double p; while(~scanf(&quot;%lf&quot;, &amp;p)) printf(&quot;%.3f\\n&quot;, solve(0, 3, p)); // 在[0,3]内查找f(x)=p的x return 0; } F 最大池化层 难度 考点 3 二维数组 循环 题目分析 这道题在用二重循环读入矩阵后，需要用一个二重循环将矩阵以 3×33×33×3 子矩阵为单位进行操作，我们将变量 i 和 j 初始化为 111 且每次加 333 ，这样每次循环得到的 i 和 j 为子矩阵最中间的元素位置。随后在循环中再用一个二重循环去读取子矩阵的各个元素，找到最大值并输出。 需要注意的是，由于最大值可能为负数，最大值的初值应当设置为int 范围的最小值（矩阵元素的值的最小值） −2147483648-2147483648−2147483648 （十六进制下为 0x80000000），或者子矩阵某个元素的值。 示例代码 #include&lt;stdio.h&gt; int in[405][405]; int main() { int m, n; scanf(&quot;%d%d&quot;, &amp;m, &amp;n); for (int i = 0; i &lt; m; ++i) { for (int j = 0; j &lt; n; ++j) { scanf(&quot;%d&quot;, &amp;in[i][j]); } } for (int i = 1; i &lt; m; i += 3) { for (int j = 1; j &lt; n; j += 3) { // 所得到的i和j为子矩阵最中间的元素位置 int max = 0x80000000; // max = in[i][j]; 也可以 // 找3*3的矩阵中的最大值 for (int k = i - 1; k &lt;= i + 1; ++k) { for (int l = j - 1; l &lt;= j + 1; ++l) { if (in[k][l] &gt; max) { max = in[k][l]; } } } printf(&quot;%d &quot;, max); } printf(&quot;\\n&quot;); } return 0; } Author：pyhAuthor：pyhAuthor：pyh G 幻彩迷宫 难度 考点 4 二维数组 题目分析 本题按照题目进行模拟填数即可。 首先，类比数学中的矩阵，可以用一个二维数组来储存题目中的方阵，注意到题目中 nnn 最大为 100100100，所以我们可以声明 int a[105][105] 数组。 然后开始填数。填数从右上角开始向下填数，直到到达最下端，随后向左填数，依次类推，直到把数填完。整体填数呈现”下，左，上，右“的规律。 示例代码 1 #include &lt;stdio.h&gt; #include &lt;string.h&gt; int a[105][105]; int main() { int n, total = 1; // total记录当前数的大小 scanf(&quot;%d&quot;, &amp;n); memset(a, 0, sizeof(a)); // 把数组元素都置0 int x = 0, y = n - 1; // 初始位置为右上角，即第0行第n-1个 a[0][n - 1] = 1; // 在起始位置填上数1 while (total &lt; n * n) { while (x + 1 &lt; n &amp;&amp; !a[x + 1][y]) //向下 a[++x][y] = ++total; // 更新total，并填在新的位置上 while (y - 1 &gt;= 0 &amp;&amp; !a[x][y - 1]) //向左 a[x][--y] = ++total; while (x - 1 &gt;= 0 &amp;&amp; !a[x - 1][y]) //向上 a[--x][y] = ++total; while (y + 1 &lt; n &amp;&amp; !a[x][y + 1]) //向右 a[x][++y] = ++total; } // 输出结果 for (int i = 0; i &lt; n; i++) { for (int j = 0; j &lt; n; j++) { printf(&quot;%-6d&quot;, a[i][j]); //输出6位，左对齐 } printf(&quot;\\n&quot;); } } 示例代码 2 利用一些技巧的实现。 #include &lt;stdio.h&gt; int a[105][105]; int step[4][2] = {{1, 0}, {0, -1}, {-1, 0}, {0, 1}}; // 分别为向下、左、上、右四个方向走一步，下标x和y的变化 int main() { int n, k = 0; scanf(&quot;%d&quot;, &amp;n); int x = 1, y = n; // 初始在右上角 for(int i = 0; i &lt;= n + 1; ++i) // 将边界都置为-1 a[0][i] = a[n + 1][i] = a[i][0] = a[i][n + 1] = -1; for(int i = 1; i &lt;= n * n; ++i) { a[x][y] = i; // 将当前位置填上当前的数 if(a[x + step[k][0]][y + step[k][1]] != 0) // 如果下一步走到边界上或者走到已经填过的位置 k = (k + 1) % 4; // 换方向 x += step[k][0], y += step[k][1]; // 更新位置为下一步 } for(int i = 1; i &lt;= n; ++i) { for(int j = 1; j &lt;= n; ++j) printf(&quot;%-6d&quot;, a[i][j]); puts(&quot;&quot;); } return 0; } 示例代码 3 本题还能用递归方法解决，缺点是时间复杂度较高。 我们可以写一个函数 int f(int i, int j, int n) 来打印当迷宫为 n×nn\\times nn×n 时第 iii 行，第 jjj 列的元素。 因为给定一个 nnn，我们可以比较方便地求出最外一圈应该填的数，而对于最外一圈以内的数，相当于迷宫规模变成 (n−2)×(n−2)(n-2)\\times (n-2)(n−2)×(n−2) ,但右上角填数时不再是从 111 开始，而是从迷宫规模为 n×nn\\times nn×n​ 时最外一圈所填最后一个数的下一个数开始，代码如下。 #include &lt;stdio.h&gt; int f(int i, int j, int n) { if (j == n) //最外圈的右边 return i; else if (i == n) //最外圈的下边 return n + (n - j); else if (j == 1) //最外圈的左边 return 2 * n - 1 + (n - i); else if (i == 1) //最外圈的上边 return 3 * n - 3 + j; else //将问题回到(n-2)*(n-2)规模上考虑 return f(i - 1, j - 1, n - 2) + (4 * n - 4); } int main(){ int n; scanf(&quot;%d&quot;, &amp;n); for (int i = 1; i &lt;= n; i++) { for (int j = 1; j &lt;= n; j++) { printf(&quot;%-6d&quot;, f(i, j, n)); } printf(&quot;\\n&quot;); } } H 多项式相减 难度 考点 4 合并数组，数组越界 题目分析 对于本道题，如果多项式的每⼀项指数部分都在 [0,105][0,10^5][0,105] 这样的范围内，则我们只需要设置⼀个数组 int coe[100005]，每次读⼊的时候以指数部分作为数组下标，将系数记录进 coe 数组中即可完成任务。但本题中指数范围过大，直接采取上述方法进行计算将会导致数组越界，因此需要考虑其他方法。 注意到 f(x)f(x)f(x) 和 g(x)g(x)g(x) 都是以严格降序给出的，因此可以考虑这样⼀种做法： 用两个变量 i,ji,ji,j 记录当前正在处理 f(x)f(x)f(x) 的第 iii 项和 g(x)g(x)g(x) 的第 jjj 项，最初 i=j=0i=j=0i=j=0。 比较 f(x)f(x)f(x) 的第 iii 项和 g(x)g(x)g(x) 的第 jjj 项的指数： a. 若指数部分相同，说明这两项应该合并，系数应相减，输出，然后将 i,ji,ji,j 都向后移动⼀位； b. 若 f(x)f(x)f(x) 的第 iii 项的指数较大，说明只有 f(x)f(x)f(x) 的第 iii 项在当前计算的这一项中出现，输出，并将 iii 向后移动⼀位； c. 若 g(x)g(x)g(x) 的第 jjj 项的指数较大，说明只有 g(x)g(x)g(x) 的第 jjj 项在当前计算的这一项中出现，输出，并将 jjj 向后移动⼀位。 当 i,ji,ji,j 将 f(x),g(x)f(x),g(x)f(x),g(x) 的每一项都计算之后，最后结果的多项式也被计算出来了。可以发现，通过该方法得到的多项式，其指数部分也是严格递减的。 由于只会移动最多 M+NM+NM+N 次，因此总循环次数不超过 M+NM+NM+N 次，可以在题目要求的时间范围内完成计算。 另外需要注意的是，本题的系数可能超出 int 范围，因此需要采用 long long 进行计算。 示例代码 #include &lt;stdio.h&gt; int r1[100005], r2[100005]; // f(x)和g(x)每项的指数 int k1[100005], k2[100005]; // f(x)和g(x)每项的系数 int main() { int m, n; // f(x)和g(x)的项数 scanf(&quot;%d&quot;, &amp;m); for(int i = 0; i &lt; m; ++i) scanf(&quot;%d%d&quot;, &amp;k1[i], &amp;r1[i]); scanf(&quot;%d&quot;, &amp;n); for(int j = 0; j &lt; n; ++j) scanf(&quot;%d%d&quot;, &amp;k2[j], &amp;r2[j]); int i = 0, j = 0; // 初始为0 while(i &lt; m || j &lt; n) // 若f(x)和g(x)没有都遍历完，则应该继续循环遍历 { long long k; // 存储当前计算的系数 int r; // 存储当前的指数 if(j == n || i &lt; m &amp;&amp; r1[i] &gt; r2[j]) // 情况b，g(x)已经全部遍历完，或者当前f(x)的项指数较大 { k = k1[i]; r = r1[i]; i++; } else if(i == m || j &lt; n &amp;&amp; r1[i] &lt; r2[j]) // 情况c，f(x)已经全部遍历完，或者当前g(x)的项指数较大 { k = -k2[j]; // f(x)-g(x)，因此这里要取相反数 r = r2[j]; j++; } else // 情况a，f(x)和g(x)的项指数相等 { // 计算f(x)-g(x)当前项的系数 k = (long long)k1[i] - k2[j]; // 注意要用long long r = r1[i]; i++, j++; } if(k) // 若k不为0，则输出 printf(&quot;%lld %d\\n&quot;, k, r); } return 0; } I Hikari 数 难度 考点 5 动态规划 题目分析 考虑一个 Hikari 数的第 iii 位。第 iii 位能放什么数字，首先和这一位是奇数位还是偶数位有关，其次和这一位的上一位，也就是第 i−1i-1i−1 位放着什么数字有关。假设第 iii 位放数字 jjj，第 i−1i-1i−1 位放数字 kkk，则需要满足 j+k≤mj + k \\le mj+k≤m 有了以上限制要求之后我们可以初步写出状态转移方程。以 dp[i][j]dp[i][j]dp[i][j] 表示长度为 iii，最高位为 jjj 的 Hikari 数的总数，那么 dp[i][j]=∑k=0min⁡(m−j,9)dp[i−1][k]dp[i][j] = \\sum _{k=0}^{\\min(m-j, 9)} dp[i - 1][k] dp[i][j]=k=0∑min(m−j,9)​dp[i−1][k] 在代码中实现这个过程时，还需注意到 jjj 和 kkk 在其数位上的限制。即 jjj 与 iii 同奇偶，kkk 与 i−1i-1i−1 同奇偶。 给定状态转移方程后，还需要一个初始化状态。比较直观的一个思路是初始化 i=1i=1i=1 的状态，将表示小于等于 mmm 的奇数的 dp 数组元素置 111。不过更优雅的方式是初始化 i=0i=0i=0 的状态，即长度为 000 的 Hikari 数，可以认为这样的数只有一个，且其最高位为 000。 又注意到各个数字是交替使用的，当最高位是奇数数位时，偶数数位并不会被更新，因此我们的思考过程虽然是 dp[i][j]dp[i][j]dp[i][j]，但实际实现过程中我们并不需要真的开 dp[1000005][10] 这么大的数组，只需 dp[10] 即可模拟以上过程。（本题没有卡空间，因此如果你开了大数组当然也能过） 由上述过程，可得其时间复杂度为 O(n)\\mathrm{O}(n)O(n)，空间复杂度为 O(1)\\mathrm{O}(1)O(1)，或者不做任何优化的情况下，空间复杂度也是 O(n)\\mathrm{O}(n)O(n)。 示例代码 1 #include &lt;stdio.h&gt; int mod = 998244353; int main(void) { int digit[10] = {1}, m, n, i, j, k; scanf(&quot;%d%d&quot;, &amp;n, &amp;m); for (i = 1; i &lt;= n; i++) { // n次迭代，每次更新digit的奇数位或偶数位上的数 for (j = i % 2; j &lt; 10; j += 2) { for (k = (i + 1) % 2; k &lt; 10; k += 2) { if (j + k &lt;= m) // 最高位为 j, 次高位为 k, 总长度为 i { digit[j] = (digit[j] + digit[k]) % mod; } } } for (k = (i + 1) % 2; k &lt; 10; k += 2) digit[k] = 0; // 这里要将与 i 不同奇偶的数字置 0 } int ans = 0; for (i = 1; i &lt; 10; i++) // 注意最高位不能为 0 { ans = (ans + digit[i]) % mod; } printf(&quot;%d&quot;, ans); return 0; } 如果对以上代码理解有困难，可以看看下面这版没有优化，全部按照思维过程写的代码。 #include &lt;stdio.h&gt; int dp[1000005][10]; int mod = 998244353; int main(void) { int n, m, i, j, k; scanf(&quot;%d%d&quot;, &amp;n, &amp;m); for (i = 1; i &lt; 10; i += 2) // 初始化长度为 1 的 Hikari 数的数量 { if (i &lt;= m) { dp[1][i]++; } } for (i = 2; i &lt;= n; i++) { for (j = i % 2; j &lt; 10; j += 2) { for (k = (i + 1) % 2; k &lt; 10; k += 2) { if (j + k &lt;= m) // 最高位为 j, 次高位为 k, 总长度为 i { dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mod; } } } } int ans = 0; for (i = 1; i &lt; 10; i++) // 注意最高位不能为 0 { ans = (ans + dp[n][i]) % mod; } printf(&quot;%d&quot;, ans); return 0; } 拓展思考 如果本题 nnn 的范围是 1≤n≤10181\\le n \\le 10^{18}1≤n≤1018，那该如何解决本题呢？ 难度 考点 7 动态规划，矩阵快速幂 本题的最优时间复杂度其实是 O(log⁡n)\\mathrm{O}(\\log n)O(logn)，以下给出助教头子 $\\mathcal{David} $ 的解题思路。 对于这样一种状态转移过程，我们可以用矩阵的形式表达出来。矩阵的行，从上到下代表数字 0,2,4,6,80,2,4,6,80,2,4,6,8。矩阵的列，从左到右表示数字 1,3,5,7,91,3,5,7,91,3,5,7,9。对于矩阵中的每一个元素，其意义为：对于它所对应的行与列的两个数字，它们在 Hikari 数中能够相邻吗？显然这仅与 mmm 的大小有关。以 m=9m=9m=9 的情况为例，其状态转移矩阵为： A=[1111111110111001100010000]A=\\begin{bmatrix} 1 &amp; 1 &amp; 1 &amp; 1 &amp; 1\\\\ 1 &amp; 1 &amp; 1 &amp; 1 &amp; 0\\\\ 1 &amp; 1 &amp; 1 &amp; 0 &amp; 0\\\\ 1 &amp; 1 &amp; 0 &amp; 0 &amp; 0\\\\ 1 &amp; 0 &amp; 0 &amp; 0 &amp;0 \\end{bmatrix} A=⎣⎢⎢⎢⎢⎡​11111​11110​11100​11000​10000​⎦⎥⎥⎥⎥⎤​ 可以注意到它总是沿主对角线对称的，也就是说，不管是行代表偶数，列代表奇数，还是行代表奇数，列代表偶数，其状态转移矩阵是完全一致的。 有了这个矩阵之后我们能干嘛呢？我们用一个向量（1 行 5 列的矩阵）来表示当前的状态，这 555 个元素分别代表最高位为 1,3,5,7,91,3,5,7,91,3,5,7,9 或 0,2,4,6,80,2,4,6,80,2,4,6,8 的 Hikari 数的个数。前面我们提到，初始状态可以看作最高位能且仅能是 000 的 000 位 Hikari 数，即初始状态矩阵为： P0=[10000]P_0 = \\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 0 &amp;0 \\end{bmatrix} P0​=[1​0​0​0​0​] 每一次的状态转移可以看作用本次的状态矩阵与转移矩阵相乘，即 Pi=Pi−1AP_i = P_{i-1}A Pi​=Pi−1​A n=2,m=9n=2,m=9n=2,m=9 时，我们有： P1=P0A=[11111]P2=P1A=[54321]P_1=P_0A=\\begin{bmatrix} 1 &amp; 1 &amp; 1 &amp; 1 &amp;1 \\end{bmatrix}\\\\ P_2=P_1A=\\begin{bmatrix} 5 &amp; 4 &amp; 3 &amp; 2 &amp;1 \\end{bmatrix} P1​=P0​A=[1​1​1​1​1​]P2​=P1​A=[5​4​3​2​1​] 由于最高位不能是 000，因此最终结果是 4+3+2+1=104+3+2+1=104+3+2+1=10。 因此，这个问题就转化成了根据 P0P_0P0​ 和状态转移矩阵 AAA 求解 PnP_nPn​。 似乎到这里，本题的时间复杂度依然是 $\\mathrm{O}(n) $，而且还多了很多废脑子的转换，可是矩阵乘法是有结合律的，因此有 Pn=P0(An)P_n=P_0(A^n) Pn​=P0​(An) 由于 P0=[10000]P_0 = \\begin{bmatrix}1 &amp; 0 &amp; 0 &amp; 0 &amp;0\\end{bmatrix}P0​=[1​0​0​0​0​]，因此 PnP_nPn​ 就是 AnA^nAn 的第一行。 而计算矩阵的幂也有类似计算数的幂一样的快速幂算法！具体实现可以参考 E4-B 的快速幂算法进行修改，使其能够计算矩阵的幂。为了方便函数传参，使用了后面的知识点“结构体”来实现，建议在学过结构体之后再来回顾。大家可以比较整数快速幂和矩阵快速幂的代码，方便理解。 整数快速幂： // 快速计算a^n（模p意义下） long long qpow(long long a, unsigned long long n, long long p) { long long b = 1; // 初始化为 1 while(n) { if (n &amp; 1) b = a * b % p; a = a * a % p; n &gt;&gt;= 1; } return b; } 矩阵快速幂： // 快速计算A^n，其中unit()函数的功能是生成并返回单位矩阵，mult函数的功能是计算并返回两个矩阵的乘积 struct Matrix qpow(struct Matrix A, long long n) { struct Matrix B = unit(); // 初始化为单位阵 while(n) { if(n &amp; 1) B = mult(A, B); A = mult(A, A); n &gt;&gt;= 1; } return B; } 最后，利用矩阵快速幂，可以将本题的时间复杂度降至 O(log⁡n)\\mathrm{O}(\\log n)O(logn)。也就是说，即使 nnn 的范围达到了 long long（101810^{18}1018 量级），该算法也完全可以在时间限制内解决问题，而时间复杂度为 O(n)\\mathrm{O}(n)O(n) 的算法就无法在可接受的时间内解决了。 欢迎大佬们尝试本方法，如果成功的话，本题的运行时间将从 100ms 左右下降到 10ms 以内。 示例代码 2 不作要求，算法难度较大，并且用到了后面才学的结构体知识。 #include &lt;stdio.h&gt; #define mod 998244353 struct Matrix { long long a[5][5]; }; struct Matrix unit() // 生成单位矩阵的函数 { struct Matrix A = {}; for(int i = 0; i &lt; 5; ++i) A.a[i][i] = 1; return A; } struct Matrix mult(struct Matrix A, struct Matrix B) // 计算矩阵A与B的乘法（模意义下） { struct Matrix C = {}; for(int i = 0; i &lt; 5; ++i) for(int j = 0; j &lt; 5; ++j) for(int k = 0; k &lt; 5; ++k) C.a[i][j] = (C.a[i][j] + A.a[i][k] * B.a[k][j]) % mod; return C; } struct Matrix qpow(struct Matrix A, long long n) { struct Matrix B = unit(); // 初始化为单位阵 while(n) { if(n &amp; 1) B = mult(A, B); A = mult(A, A); n &gt;&gt;= 1; } return B; } int main() { long long n, ans = 0; int m; scanf(&quot;%lld%d&quot;, &amp;n, &amp;m); // 构建转移矩阵A struct Matrix A = {}; for(int i = 0; i &lt; 5; ++i) // i=0,1,2,3,4分别代表0,2,4,6,8（i对应2i） for(int j = 0; j &lt; 5; ++j) // j=0,1,2,3,4分别代表1,3,5,7,9（j对应2j+1） if(2 * (i + j) + 1 &lt;= m) // (2i)+(2j+1)&lt;=m A.a[i][j] = 1; A = qpow(A, n); // 计算A^n，并赋给A // 计算答案，累加Pn的各状态，Pn就是A的第0行 // 如果n为偶数，累加最高位为2,4,6,8的状态（对应下标为1,2,3,4） // 如果n为奇数，累加最高位为1,3,5,7,9的状态（对应下标为0,1,2,3,4） for(int i = (n + 1) % 2; i &lt; 5; ++i) ans = (ans + A.a[0][i]) % mod; printf(&quot;%lld&quot;, ans); return 0; } J czx 的消除游戏 难度 知识点 6~7 区间 dp 题目分析 区间动态规划典题。令 dp[i][j] 表示消除区间 [i,j][i, j][i,j] 内的方块得到的最大分数。 区间 dp 的经典套路是，第一层循环从小到大枚举区间长度，第二层循环枚举起点，计算得出终点。 或者，第一层循环从小到大枚举区间右端点，第二层循环从小到大枚举区间左端点。 保证之后枚举的区间的所有子区间已经在之前被枚举过即可。 考虑如何转移得到 dp[i][j]。首先不考虑消除的情况，直接把当前区间分割成两半，相应的得分为 dp[i][mid] + dp[mid + 1][j]。 然后，如果区间的两端颜色相同，那么可以考虑双消/三消。 双消：消除中间的区间，再双消两端的同颜色块 三消：枚举找出区间中间和两端颜色相同的块，以这个块为中心，消除左边和右边的区间，再三消两端和中心块 这样，就枚举得到了这个区间的所有消除情况，求出其最大值即可。 最后，dp[1][n] 即为答案。 示例代码 1 枚举区间的顺序为：按照区间长度从小到大枚举，相同长度的区间从左到右枚举。 #include &lt;stdio.h&gt; #define N 505 #define max(a, b) ((a) &gt; (b) ? (a) : (b)) int dp[N][N]; // 表示 [i, j] 的最大价值 int n, a[N], value[5]; int main() { scanf(&quot;%d&quot;, &amp;n); for (int i = 1; i &lt;= 3; i++) { scanf(&quot;%d&quot;, value + i); } for (int i = 1; i &lt;= n; i++) { scanf(&quot;%d&quot;, &amp;a[i]); dp[i][i] = value[1]; // 边界 } for (int l = 2; l &lt;= n; l++) { // 第一层，状态的区间长度 for (int i = 1; i + l - 1 &lt;= n; i++) { // 第二层，状态的起始位置 int j = i + l - 1; for (int k = i; k &lt; j; k++) { dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j]); } if (a[i] == a[j]) { dp[i][j] = max(dp[i][j], dp[i + 1][j - 1] + value[2]); for (int k = i + 1; k &lt; j; k++) { if (a[k] == a[i] &amp;&amp; a[k] == a[j]) { dp[i][j] = max(dp[i][j], dp[i + 1][k - 1] + dp[k + 1][j - 1] + value[3]); } } } } } printf(&quot;%d&quot;, dp[1][n]); return 0; } 示例代码 2 枚举区间的顺序为：按照区间右端点从小到大的顺序枚举，相同右端点的区间，按照左端点从大到小的顺序枚举。 #include &lt;stdio.h&gt; int max(int a, int b) { return a &gt; b ? a : b; } int dp[205][205]; int color[205]; int main(void) { int n, a, b, c; scanf(&quot;%d%d%d%d&quot;, &amp;n, &amp;a, &amp;b, &amp;c); for(int i = 0; i &lt; n; ++i) scanf(&quot;%d&quot;, &amp;color[i]); for(int j = 0; j &lt; n; ++j) // 第一层循环从小到大枚举区间右端点 { dp[j][j] = a; // 边界 int k[205], cnt = 0; // 数组k用于记录枚举i时，与j同颜色的i，cnt用于记录与j同颜色的i的个数 for(int i = j - 1; i &gt;= 0; --i) // 第二层从大到小枚举区间左端点 { if(color[i] == color[j]) // 若左右端点颜色相同 { dp[i][j] = max(dp[i][j - 1] + a, dp[i + 1][j - 1] + b); // 右端点j单消，或者左右端点ij双消 for(int l = 0; l &lt; cnt; ++l) // 枚举i与j之间的同颜色的位置k[l] { dp[i][j] = max(dp[i][j], dp[i][k[l] - 1] + dp[k[l]][j]); // k[l]与j双消 dp[i][j] = max(dp[i][j], dp[i + 1][k[l] - 1] + dp[k[l] + 1][j - 1] + c); // i,k[l],j三消 } k[cnt++] = i; //更新k和cnt } else // 左右端点颜色不同 { dp[i][j] = dp[i][j - 1] + a; // 右端点j单消 for(int l = 0; l &lt; cnt; ++l) dp[i][j] = max(dp[i][j], dp[i][k[l] - 1] + dp[k[l]][j]); // k[l]与j双消 } } } printf(&quot;%d&quot;, dp[0][n - 1]); return 0; } - End - ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"C5 - Solution-23航C","link":"https://github.pansis.site/post/C5 - Solution-23航C/","stats":{"text":"36 min read","time":2111000,"words":7671,"minutes":36},"dateFormat":"2024-04-21"},{"content":"A 一般通过计算式 难度 考点 1 库函数的使用 题意分析 按照题意，我们需要引入 math.h 库并调用其中的库函数 exp , cos , atan , log , cosh来进行计算。可以得到如下示例代码。注意输入为不定行输入，输出保留三位小数。 示例代码 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main(void) { double x, y; while (scanf(&quot;%lf&quot;, &amp;x) != EOF) { y = exp(cos(atan(x))) / log(cosh(x) + 1); printf(&quot;%.3f\\n&quot;, y); } return 0; } B a^b Problem Ver.⑨ 难度 考点 1 函数调用 问题分析 直接调用函数即可。 参考代码 #include &lt;stdio.h&gt; // 宏定义，long long太长了 #define LL long long LL qpow(LL a, LL b, LL p) { LL ans = 1; a = a % p; while (b) { if (b &amp; 1) ans = (ans * a) % p; b &gt;&gt;= 1; a = a * a % p; } return ans; } int main() { LL a, b, p; scanf(&quot;%lld%lld%lld&quot;, &amp;a, &amp;b, &amp;p); printf(&quot;%lld&quot;, qpow(a, b, p)); return 0; } C 三数gcd与lcm 难度 考点 2 函数 题目分析 利用辗转相除法计算出 gcd 与 lcm 即可。如果实在感觉难以理解，也可以先记住本题的题解，作为模板随时调用。 本题有如下几个注意事项： 注意数据范围，输入的是 int 型不代表函数调用是 int 型，调用 gcd 与 lcm 函数时，函数的输入有可能是 long long 类型，因为计算出前两个数的最小公倍数（lcm）之后，这个数可能超过 long long 的范围，这个数还要继续参与最小公倍数的计算，而要计算最小公倍数（lcm）还要先计算最大公约数（gcd），因此 gcd 的输入也可能是 long long 类型。 如果上面这段话难以理解，本题可以全开 long long ，当同学们认为一道题可能出现 long long 范围的数据时，就可以全开 long long ，除非是位运算等题目规定了数据范围。 本题为了防止溢出，在 lcm 环节必须先除后乘。因为 gcd⁡(a,b)\\gcd (a,b)gcd(a,b) 一定是 aaa 和 bbb 的因子，因此不会出错。先做乘法有可能导致 a×ba \\times ba×b 先溢出了，导致不溢出的最终结果出错了。 如果上面这段话难以理解，可以思考这样一个场景：算式 4×3÷24\\times 3\\div 24×3÷2 与 4÷2×34\\div 2\\times 34÷2×3 结果应该是相同的，有一台数据上限为 101010 的计算机，如果我先计算 4×34\\times 34×3 则会先溢出，如果我先计算 4÷24\\div 24÷2 ，则不会溢出。因此，对于数学公式，应合理安排运算顺序，防止潜在的溢出。 示例代码 #include&lt;stdio.h&gt; long long gcd(long long a, long long b); long long lcm(long long a, long long b); int main() { long long m, n, q; scanf(&quot;%lld%lld%lld&quot;, &amp;m, &amp;n, &amp;q); printf(&quot;%lld\\n&quot;, gcd(q, gcd(m, n))); printf(&quot;%lld\\n&quot;, lcm(q, lcm(m, n))); return 0; } long long gcd(long long a, long long b) { while (b != 0) { long long temp = a % b; a = b; b = temp; } return a; } long long lcm(long long a, long long b) { long long c; c = a / gcd(a, b) * b ; return c; } D 这是一个汉诺塔 难度 考点 3 递归，汉诺塔 题目分析 经典递归问题之汉诺塔——课本上就有源代码 让我们再回顾其思路 假设有一个 nnn 层的汉诺塔在左侧的柱子上，要将其移动到右侧的柱子，需要怎么移动最快？ 首先，至少，我们需要将最大的，最底下的第 nnn 圆盘移动到最右侧的柱子上。 由于圆盘只能放在更大的圆盘上，因此为了将第 nnn 号圆盘移动到最右侧的柱子上，我们需要： 第一步，将前 n−1n-1n−1 个圆盘组成的汉诺塔移动到中间的柱子上 第二步，将第 nnn 个圆盘移动到最右侧柱子上 第三步，将前 n−1n-1n−1 个圆盘组成的汉诺塔移动到右侧柱子上。 第二步可以直接输出，接下来需要处理的就是第一步和第三步，注意到第 nnn 层圆盘的存在完全不会影响前 n−1n-1n−1 层圆盘的移动，那么第一步和第三步本质就是 n−1n-1n−1 层的汉诺塔的问题。 那么这 n−1n-1n−1 层的汉诺塔问题又可以再一次化归成 n−2n-2n−2 层的汉诺塔，化归成 n−3n-3n−3 层的汉诺塔……直到变成最基本的情况：只有一个圆盘，直接移动即可。 由此我们就得到了递归关系和初始状态，就能解决整个问题了。 读入字符时，要跳过字符之间的空格。可以使用语句 scanf(&quot; %c&quot;, &amp;c); 来实现，作用是跳过空白符，直到遇到一个非空白符，将其读入并存储在变量 c 中。 输出前导零时可以使用格式控制符 %02d ，表示输出至少两位的整数，不足两位则补充前导0 示例代码 #include &lt;stdio.h&gt; void hanoi(int n, char from, char via, char to); void move(int n, char from, char to); int step = 0; int main(void) { int n; char from, via, to; scanf(&quot; %c %c %c %d&quot;, &amp;from, &amp;via, &amp;to, &amp;n); hanoi(n, from, via, to); return 0; } void hanoi(int n, char from, char via, char to) { // 将n层的汉诺塔从from柱经过via柱移动到to柱上 if (1 == n) { // 只有一层的初始状态，直接输出即可 move(n, from, to); return; } hanoi(n - 1, from, to, via); // 第一步，将前n-1层组成的汉诺塔从from柱经过to柱移动到via柱上 move(n, from, to); // 第二步，移动第n层圆盘至to柱上 hanoi(n - 1, via, from, to); // 第三步，将前n-1层组成的汉诺塔从via柱经过from柱移动到to柱上 } void move(int n, char from, char to) { step++; printf(&quot;step %d : move %02d from %c to %c\\n&quot;, step, n, from, to); } E Firefly小姐的幸运四叶草 难度 考点 3 浮点数，字符画 题目分析 遍历 [x1, x2]×[y1, y2][x_1,\\ x_2]\\times[y_1,\\ y_2][x1​, x2​]×[y1​, y2​] 中的每个点，判断对应的 (x, y)(x,\\ y)(x, y) 是否满足在四叶玫瑰线线内，对应输出 ff 或空格即可。 需要注意： 嵌套循环中外层循环为 yyy ，内层循环为 xxx 。且 yyy 递减， xxx 递增。 若使用极坐标公式 r≤a∣sin⁡2θ∣r \\le a|\\sin 2\\theta|r≤a∣sin2θ∣ 进行判断，如果出现刚好满足 r=asin2θr=asin2\\thetar=asin2θ​​ 的点，由于浮点数精度问题可能导致错判为四叶玫瑰线外的点，需要使用 a &lt; b + eps 的形式进行判断。 若使用直角坐标公式 (x2+y2)3≤4a2x2y2(x^2+y^2)^3\\le4a^2x^2y^2(x2+y2)3≤4a2x2y2 进行判断，由于计算过程中会超过 int 的范围，因此要保证在 long long 范围内进行计算。 示例代码 1 极坐标 #include &lt;math.h&gt; #include &lt;stdio.h&gt; #define eps 1e-9 int main() { int x1, x2, y1, y2; int a; scanf(&quot;%d %d %d %d&quot;, &amp;x1, &amp;x2, &amp;y1, &amp;y2); scanf(&quot;%d&quot;, &amp;a); for (int y = y2; y &gt;= y1; y--) { // 每一列（y坐标） for (int x = x1; x &lt;= x2; x++) { // 每一行（x坐标） double theta = atan2(y, x), r = sqrt(x * x + y * y); if (r &lt; a * fabs(sin(2 * theta)) + eps) // 注意浮点数判等 printf(&quot;ff&quot;); else printf(&quot; &quot;); } printf(&quot;\\n&quot;); // 每行输出完要记得空行 } return 0; } 示例代码 2 直角坐标 #include &lt;stdio.h&gt; #define sq(x) ((x) * (x)) // 计算x的平方 #define cube(x) ((x) * (x) * (x)) //计算x的立方 int main() { int l, r, u, d, a; // 分别为左、右、上、下，以及参数a scanf(&quot;%d%d%d%d%d&quot;, &amp;l, &amp;r, &amp;d, &amp;u, &amp;a); for(long long y = u; y &gt;= d; y--) { for(long long x = l; x &lt;= r; ++x) { if(cube(sq(x) + sq(y)) &lt;= 4 * sq(a * x * y)) printf(&quot;ff&quot;); else printf(&quot; &quot;); } printf(&quot;\\n&quot;); } return 0; } F 三质数之谜 难度 知识点 4 分解质因数 题目分析 本题要求我们判断一个数是否可以恰好由三个质数相乘得到。我们可以通过分解质因数的方法来解决这个问题。对于一个数 nnn ，我们从 222 开始，尝试将 nnn 分解为质因数的乘积，判断 $ n$ 是否可以被分解为恰好三个质数的乘积。 分解质因数算法的步骤如下： 从最小的质数 222 开始，尝试将 nnn 除以当前的数 iii 。 如果 nnn 可以被 iii 整除，那么 nnn 一定是 iii 的倍数，我们可以将 nnn 除以 iii ，直到 nnn 不能被 iii 整除。每除一次，我们就找到了一个质因数，记录下来。 当 nnn 不能再被 iii 整除时，我们继续尝试下一个数。 重复上述步骤，直到 i2i^2i2 大于 nnn。 如果 nnn 此时为 111，说明分解完成；如果 nnn 不为 111，说明 nnn 本身是一个质数，将其记录下来，分解完成。 示例代码 #include &lt;stdio.h&gt; int prime_factors_num(long long x) // 计算参数x的质因数个数 { int cnt = 0; for(long long i = 2; i * i &lt;= x; i++) { while(x % i == 0) { x /= i; cnt++; } } if(x != 1) cnt++; return cnt; } void solve() //处理每组数据 { long long n; scanf(&quot;%lld&quot;, &amp;n); puts(prime_factors_num(n) == 3 ? &quot;YES&quot; : &quot;NO&quot;); } int main() { int T; scanf(&quot;%d&quot;, &amp;T); while (T--) // T组数据 { solve(); } return 0; } G 飞彳亍棋 难度 考点 3~4 递推， 递归 题意分析 Hint 中的提示应已足够详细。 Hint 请使用 double 类型的变量存储浮点数。 设 P(n)P(n)P(n) 代表恰落在第 nnn 格上的概率。显然初始时已知 P(0)=1P(0)=1P(0)=1。为便利，定义对 ∀x&lt;0\\forall x&lt;0∀x&lt;0，有 P(x)=0P(x)=0P(x)=0。我们可以： 从递推的角度考虑。若已经到达第 nnn 格，则在此基础上再掷一次后有 1s\\frac{1}{s}s1​ 的概率分别落在第 n+1,n+2,…,n+sn+1,n+2, \\dots ,n+sn+1,n+2,…,n+s 格上。换言之，P(n)P(n)P(n) 能够给 P(n+1),P(n+2),…,P(n+s)P(n+1),P(n+2), \\dots , P(n+s)P(n+1),P(n+2),…,P(n+s) 各贡献 P(n)s\\frac{P(n)}{s}sP(n)​ 的概率。 或从递归的角度考虑，若想到达第 nnn 格，必然要： 先到达第 n−s,n−s+1,…,n−1n-s,n-s+1, \\dots ,n-1n−s,n−s+1,…,n−1 格之一。这些事件的概率分别为 P(n−s),P(n−s+1),…,P(n−1)P(n-s),P(n-s+1), \\dots ,P(n-1)P(n−s),P(n−s+1),…,P(n−1)； 再通过分别掷出 s,s−1,…,1s,s-1, \\dots ,1s,s−1,…,1 到达第 nnn 格。各事件概率均为 1s\\frac{1}{s}s1​。 相乘再相加，可得出下列公式： P(n)=∑i=n−sn−1P(i)∗1sP(n)=\\sum_{i=n-s}^{n-1} P(i)*\\frac{1}{s} P(n)=i=n−s∑n−1​P(i)∗s1​ 示例代码 1 递推思路 #include&lt;stdio.h&gt; int n, s; double P[20]; int main() { scanf(&quot;%d%d&quot;, &amp;s, &amp;n); P[0] = 1.0; for (int i = 0; i &lt;= n; i++) //当i循环到某一值时，P[i]里已经存储了正确的P(i) { for (int j = i + 1; j &lt;= i + s; j++) //落在i处后，下一回合可能落在这些j处 P[j] += P[i] / s; //加上P[i]对P[j]的贡献 } printf(&quot;%.6lf&quot;, P[n]); return 0; } 示例代码 2 递归思路 #include&lt;stdio.h&gt; int n, s; double P(int); int main() { scanf(&quot;%d%d&quot;, &amp;s, &amp;n); printf(&quot;%.6lf&quot;, P(n)); return 0; } double P(int x) { if (x &lt; 0) return 0; if (x == 0) return 1; //两种边界条件 double ans = 0; for (int i = x - s; i &lt;= x - 1; i++) ans += P(i) / s; //为落在x上，需先落在i上（概率为P[i])，再掷出x-i(概率为1/s)，故概率相乘 //只要对某一个i发生上述二事件即可，故不同i之间概率相加 return ans; } 示例代码 3 记忆化递归 #include &lt;stdio.h&gt; double P[15]; double p(int s, int n) { // 基本情况1：n&lt;0时返回0 if(n &lt; 0) return 0.0; // 基本情况2：若计算过P[n]，则直接返回，无需再递归调用 if(P[n] &gt; 0) return P[n]; // 没计算过P[n]的情况： // 基本情况：n=0时，返回1.0 if(n == 0) return P[n] = 1.0; // 一般情况：计算P[n] for(int i = 1; i &lt;= s; ++i) P[n] += p(s, n - i) / s; return P[n]; } int main() { int s, n; scanf(&quot;%d%d&quot;, &amp;s, &amp;n); printf(&quot;%f&quot;, p(s, n)); return 0; } H Cantor Set 难度 考点 5 递归输出 题目分析 分形图案的特征就是具备“自相似性”，而康托集正是最基础的分形图案。 而递归就是把大问题分解为相同类型的小问题，我们可以使用递归函数输出一个分形图案。 思路一 设函数 f(n,k)f(n, k)f(n,k) 的功能是“输出 nnn 次迭代中第 kkk 次迭代的一整行”，则 f(n,k)f(n,k)f(n,k) 的递归方案为： 当 k=0k = 0k=0 时，输出 3n3^n3n 个字符 -； 当 k&gt;0k &gt; 0k&gt;0 时，调用 f(n−1,k−1)f(n-1, k - 1)f(n−1,k−1)，然后输出 3n−13^{n-1}3n−1 个空格，然后再调用 f(n−1,k−1)f(n-1, k - 1)f(n−1,k−1)； 示例代码 1 #include &lt;stdio.h&gt; int power_3[10] = {1}; void f(int n, int k) // 输出n次迭代中第i次迭代的一整行（不输出换行） { if(k == 0) { for(int i = 0; i &lt; power_3[n]; ++i) printf(&quot;%c&quot;, '-'); } else { f(n - 1, k - 1); for(int i = 0; i &lt; power_3[n - 1]; ++i) printf(&quot;%c&quot;, ' '); f(n - 1, k - 1); } } int main() { int n; scanf(&quot;%d&quot;, &amp;n); for(int i = 1; i &lt;= n; ++i) // 求出3的幂（每部分的字符个数） power_3[i] = 3 * power_3[i - 1]; for(int i = 0; i &lt;= n; ++i) { f(n, i); printf(&quot;\\n&quot;); // 每输出一行后要换行 } return 0; } 思路二 设函数 f(n,i,j)f(n, i, j)f(n,i,j) 的功能是“返回 nnn 次迭代中第 iii 次迭代的一行中的第 jjj 个字符”，则 f(n,i,j)f(n, i, j)f(n,i,j) 的递归方案为： 若 i=0i=0i=0，一整行都是字符 -，因此返回字符 -； 若 i&gt;0i&gt;0i&gt;0，当该字符在一行的中间去掉的部分时，即 3n−1&lt;j≤2×3n−13^{n-1} &lt; j \\le 2\\times 3^{n-1}3n−1&lt;j≤2×3n−1，返回空格符； 若 i&gt;0i&gt;0i&gt;0，当该字符在一行的左侧部分时，即 1≤j≤3n−11\\le j\\le 3^{n-1}1≤j≤3n−1，返回 f(n−1,i−1,j)f(n-1,i-1,j)f(n−1,i−1,j)； 若 i&gt;0i&gt;0i&gt;0，当该字符在一行的右侧部分时，即 2×3n−1&lt;j≤3n2\\times 3^{n-1}&lt;j\\le3^n2×3n−1&lt;j≤3n，返回 f(n−1,i−1,j−2×3n−1)f(n-1,i-1,j - 2\\times 3^{n-1})f(n−1,i−1,j−2×3n−1)。 如果下标 jjj 从第 000 个开始记，则第二种情况的条件可以写作 ⌊j3n−1⌋=1\\left\\lfloor\\frac j{3^{n-1}}\\right\\rfloor=1⌊3n−1j​⌋=1；后两种情况可以统一起来，返回值可以写作 f(n−1,i−1,j mod 3n−1)f(n-1,i-1,j\\bmod 3^{n-1})f(n−1,i−1,jmod3n−1)。 示例代码 2 #include &lt;stdio.h&gt; int power_3[10] = {1}; char f(int n, int i, int j) // 返回n次迭代中第i次迭代的一行中的第j个字符 { if(i == 0) return '-'; else if(j / power_3[n - 1] == 1) return ' '; else return f(n - 1, i - 1, j % power_3[n - 1]); } int main() { int n; scanf(&quot;%d&quot;, &amp;n); for(int i = 1; i &lt;= n; ++i) // 求出3的幂（每部分的字符个数） power_3[i] = 3 * power_3[i - 1]; for(int i = 0; i &lt;= n; ++i) { for(int j = 0; j &lt; power_3[n]; ++j) printf(&quot;%c&quot;, f(n, i, j)); printf(&quot;\\n&quot;); // 每输出一行后要换行 } return 0; } 思路三 与思路一类似。 设函数 f(n,k)f(n, k)f(n,k) 的功能为： 当 k≥0k\\ge 0k≥0 时，与思路一中的 f(n,k)f(n,k)f(n,k) 功能相同，即“输出 nnn 次迭代中第 kkk 次迭代的一整行”； 当 k=−1k = -1k=−1 时，表示输出 3n3^n3n 个空格。 则 f(n,k)f(n,k)f(n,k) 的递归方案为： 当 n=0n = 0n=0 时，根据 kkk 是否为 −1-1−1 输出一个空格或一个字符 -； 当 n&gt;0n&gt;0n&gt;0 时，若 k&gt;0k &gt; 0k&gt;0，调用 f(n−1,k−1)f(n-1, k - 1)f(n−1,k−1)，然后调用 f(n−1,−1)f(n - 1, -1)f(n−1,−1) 输出 3n−13^{n-1}3n−1 个空格，然后再调用 f(n−1,k−1)f(n-1, k - 1)f(n−1,k−1)​； 当 n&gt;0n&gt;0n&gt;0 时，若 k=0k = 0k=0 或 k=−1k = -1k=−1 ，则调用三次 f(n−1,k)f(n - 1, k)f(n−1,k)​​。 示例代码 3 #include &lt;stdio.h&gt; void f(int n, int k) { if(n == 0) putchar(k == -1 ? '-' : ' '); else if(k &gt; 0) { f(n - 1, k - 1); f(n - 1, -1); f(n - 1, k - 1); } else { f(n - 1, k); f(n - 1, k); f(n - 1, k); } } int main() { int n; scanf(&quot;%d&quot;, &amp;n); for(int i = 0; i &lt;= n; ++i) { f(n, i); printf(&quot;\\n&quot;); // 每输出一行后要换行 } return 0; } I 失踪的进制 难度 考点 5 进制转换 数据类型 题意解析 一种比较朴实的做法是：遍历每一种可能的进制，转换成十进制之后进行乘法来判断是否成立。 首先要处理的一件事是：在哪些进制下是允许出现这些数字的。例如 123123123 不可能出现在三进制下，因为三进制中并不存在数字 333. 因此我们需要找到出现的最大的数字，以此判断 BBB 最小可能是多少。这一步我们可以依次提取每一位数字并进行比较来完成。注意到题中所给数据位数不超过 666 位，我们可以如下处理。 以寻找 a=810975a=810975a=810975 中最大的数字为例 int B = 2, base = 1, a = 810975; for (int i = 0; i &lt; 6; i++) { if (a \\ base % 10 &gt;= B) { B = a \\ base % 10 + 1; } base *= 10; } 在获取了 BBB 的最小值之后，我们便可遍历 BBB 的值，计算在 BBB 进制下该等式是否成立。 一种办法是将数字转化为十进制后再进行乘法运算。类似于上面的做法，由于数据位数不超过 666 位，我们可以依次提取每一位，以此来将其转化为十进制。 以将 a=810975(12)a=810975_{(12)}a=810975(12)​ 转化为十进制为例 int base = 1, base0 = 1, a = 810975, a0 = 0, B = 12; for (int i = 0; i &lt; 6; i++) { a0 += a \\ base % 10 * base0; base *= 10; base0 *= B; } ⇒a0=8×125+1×124+0×123+9×122+7×121+5×120\\Rightarrow a_0=8\\times 12^5+1\\times 12^4+0\\times 12^3+9\\times 12^2+7\\times 12^1+5\\times 12^0⇒a0​=8×125+1×124+0×123+9×122+7×121+5×120 将 a, b, ca,\\ b,\\ ca, b, c 都转化为十进制的 a0, b0, c0a_0,\\ b_0,\\ c_0a0​, b0​, c0​ 之后便可进行乘法运算。 需要注意的是,虽然 a, b, ca,\\ b,\\ ca, b, c 不超过 666 位，其转化为十进制后的最大值为 166−1=1677721516^6-1=16777215166−1=16777215 未超过 int 类型的储存上限，但是 a∗ba * ba∗b 的最大值为 167772152≈2.8×101416777215^2\\approx 2.8\\times 10^{14}167772152≈2.8×1014 超过了 int 类型的储存上限，因此需要我们改变数据类型为 long long int 来进行运算。 综合以上我们就能得到示例代码 1。 我们也可以使用函数来解决，定义一个函数 f(x,B)f(x,B)f(x,B)，该函数计算输入的 xxx 在 BBB 进制下的值，若 xxx 不能被 BBB 进制表示，则返回 −1-1−1。 循环枚举 BBB，利用函数进行判断即可。具体见示例代码 2。 如果对字符串处理有所了解，那么可以使用一种更为简洁的做法。 stdlib.h 头文件中的库函数 strtol 可以将字符串按照给定的进制转化为数字。感兴趣的同学可以自行查阅资料，这里不再详述。用此法可以得到示例代码 3。 示例代码 1 #include &lt;stdio.h&gt; int main(void) { int a, b, c; // 原数字 int B = 2; // 进制 int a0, b0, c0; // 转化成十进制的a,b,c int base, base0; // 用于计算进制转换的中间变量 scanf(&quot;%d%d%d&quot;, &amp;a, &amp;b, &amp;c); // 寻找最小的可能存在的B base = 1; for (int i = 0; i &lt; 6; i++) { if (a / base % 10 &gt;= B) { B = a / base % 10 + 1; } if (b / base % 10 &gt;= B) { B = b / base % 10 + 1; } if (c / base % 10 &gt;= B) { B = c / base % 10 + 1; } base *= 10; } // 遍历每一种进制 计算是否成立 while (B &lt;= 16) { a0 = 0; b0 = 0; c0 = 0; base = 1; base0 = 1; for (int i = 0; i &lt; 6; i++) { a0 += a / base % 10 * base0; b0 += b / base % 10 * base0; c0 += c / base % 10 * base0; base *= 10; base0 *= B; } if ((long long int)a0 * b0 == c0) { printf(&quot;%d&quot;, B); break; } B++; } // 未找到解 if (B &gt; 16) { printf(&quot;0&quot;); } return 0; } 示例代码 2 #include &lt;stdio.h&gt; // 函数f(x,B)：计算x在B进制下的值，若x不能被B进制表示，则返回-1 int f(int x, int B) { int ans = 0; for(int i = 1; x; i *= B, x /= 10) { ans += x % 10 * i; if(x % 10 &gt;= B) return -1; } return ans; } int main() { int a, b, c, B; scanf(&quot;%d%d%d&quot;, &amp;a, &amp;b, &amp;c); for(B = 2; B &lt;= 16; ++B) if (~f(a, B) &amp;&amp; ~f(b, B) &amp;&amp; ~f(c, B) // f(a,B),f(b,B),f(c,B)均不为-1 &amp;&amp; 1LL * f(a, B) * f(b, B) == f(c, B)) // 且乘法算式成立 { printf(&quot;%d&quot;, B); // 找到B，输出后结束程序 return 0; } printf(&quot;0&quot;); // 没有找到B，输出0 return 0; } 示例代码 3 #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; int main(void) { char a[10], b[10], c[10]; char *aEnd, *bEnd, *cEnd; int a0, b0, c0; scanf(&quot;%s%s%s&quot;, a, b, c); for (int B = 2; B &lt;= 16; B++) { a0 = strtol(a, &amp;aEnd, B); b0 = strtol(b, &amp;bEnd, B); c0 = strtol(c, &amp;cEnd, B); if (*aEnd || *bEnd || *cEnd) { continue; // 数字在该进制下不存在 } else if ((long long int)a0 * b0 != c0) { continue; // 等式不成立 } else { printf(&quot;%d&quot;, B); return 0; } } printf(&quot;0&quot;); // 未找到解 return 0; } J 式神们夜里不睡觉 难度 考点 5~6 进制转换 简要题意 实现任意进制转换。 问题分析 关于任意进制的转换 对于一个任意进制转换的问题，我们首先应当把待转换的数码用我们熟悉的进制来表示，即十进制，否则对于一个字符串我们几乎无法获得任何有效信息。kkk 进制转十进制的方法较为容易，可以按如下实现： long long n, len, k1, k2, i; char s1[100]; // 转换前的字符串 scanf(&quot;%lld%lld%s&quot;, &amp;k1, &amp;k2, s1); len = strlen(s1); // 获取字符串s1的长度 for (i = 0; i &lt; len; i++) { if (isupper(s1[i])) // 如果s1[i]是大写字母 n = n * k1 + s1[i] - 'A' + 10; else n = n * k1 + s1[i] - '0'; } 注意到上面使用了两个函数，一个是 strlen()，用来获取一个字符串的长度，这个函数包含在 string.h 头文件中；一个是 isupper()，用来判断一个字符是否是大写字母，这个函数包含在 ctype.h 头文件中。当然，判断大写字母也可以写成 'A'&lt;=s1[i]&amp;&amp;s1[i]&lt;='Z'。 而对于十进制转 kkk 进制，对于一般正进制的题目来说，基本思路是将待转换的数不停地对 kkk 取余，然后将余数倒序输出。然而，在负进制的背景下，这个操作有些许不同之处。 关于负进制 首先我们应该知道的是，C 语言中 % 运算的含义是对某个数取余。注意到，在 C99 标准（即 ACOJ 使用的标准）中有规定除法为趋零截尾，即将整除的结果向 000 的方向将小数部分截断。而对于 r = a % b，rrr 的计算方法一般可以表示为 r = a - a / b * b。你可以试试运行下面这个代码： #include &lt;stdio.h&gt; int main() { printf(&quot;%d\\n&quot;, -10 % -3); // 输出-1 printf(&quot;%d\\n&quot;, 10 % -3); // 输出1 return 0; } 因此，如果我们待转换的整数是一个负数，在取余的过程中余数是可能为负的，而我们不能直接将一个负余数作为转换后的结果直接输出。为了得到一个正余数，我们可以从商“借”一位到余数。 对于一个除数是负数的除法：$$(商+1) \\times 除数 + (余数-除数) = 被除数$$ 由于余数的绝对值一定小于除数的绝对值，所以我们新得到的余数一定是一个正数。在负进制的转换中，我们只要每次判断余数的正负，然后根据相应的方法进行处理即可： char s0[37] = {&quot;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;}; // 需要用到的数码 char s2[100]; // 转换后的字符串 int i = 0; while (n) { long long reNum = n % k2; // 余数 if (reNum &lt; 0) // 余数为负数 { reNum -= k2; // 余数变为正数 s2[i] = s0[reNum]; n = n / k2 + 1; // 商加1 } else { s2[i] = s0[reNum]; n /= k2; } i++; } while (i--) // 逆序输出 printf(&quot;%c&quot;, s2[i]); 当然，我们不能忘记 0 这个特殊数字，它在任意进制下的表示都为 0，特判一下就可以了。 参考代码 #include &lt;stdio.h&gt; #include &lt;ctype.h&gt; #include &lt;string.h&gt; #define LL long long char s0[37] = {&quot;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;}; int main() { char s1[100], s2[100]; LL n, k1, k2, T, len, i; scanf(&quot;%lld&quot;, &amp;T); while (T--) { n = 0; scanf(&quot;%lld%lld%s&quot;, &amp;k1, &amp;k2, s1); if (s1[0] == '0') printf(&quot;0\\n&quot;); else { len = strlen(s1); for (i = 0; i &lt; len; i++) { if (isupper(s1[i])) n = n * k1 + s1[i] - 'A' + 10; else n = n * k1 + s1[i] - '0'; } int i = 0; while (n) { LL reNum = n % k2; if (reNum &lt; 0) { reNum -= k2; s2[i] = s0[reNum]; n = n / k2 + 1; } else { s2[i] = s0[reNum]; n /= k2; } i++; } while (i--) printf(&quot;%c&quot;, s2[i]); printf(&quot;\\n&quot;); } } return 0; } K 灵梦的大清洗 难度 考点 6 递归 题目分析 本问题被称为Tantalizer Problem，方法很多，例如维护等差数列，计算递推公式等。感兴趣的同学可以自行搜索调研 本题解采用递归的做法，将大的目标转化为更小且更易解决的子目标。 放到这道题来，就是将一个从 111 到 nnn 的序列不断转化为更小的从 111 到 nnn 的序列来处理。 题目中有两种操作：从上到下消去和从下到上消去，两种操作轮流执行，得到最终剩下的数字。 记 f(n)f(n)f(n) 为第一步从上到下消去时最后剩下的数字， f′(n)f&#x27;(n)f′(n) 为第一步从下到上消去时最后剩下的数字。由对称性可知 f(n)+f′(n)=n+1f(n)+f&#x27;(n)=n+1f(n)+f′(n)=n+1 随后我们考察从上到下进行消去时的情况。此时序列中所有的奇数被消去，只剩下偶数，且他们的值正好是 111 到 ⌊n2⌋\\left\\lfloor\\dfrac{n}{2}\\right\\rfloor⌊2n​⌋ 的序列的两倍。由此可以得到 f(n)=2∗f′(⌊n2⌋)f(n)=2*f&#x27;(\\left\\lfloor\\dfrac{n}{2}\\right\\rfloor)f(n)=2∗f′(⌊2n​⌋) 由题意可知初始条件为 f(1)=f′(1)=1f(1)=f&#x27;(1)=1f(1)=f′(1)=1 由上可以计算得到： f(n)={1n=12∗(⌊n2⌋+1−f(⌊n2⌋))n&gt;1f(n)=\\begin{cases}1&amp;n=1\\\\ 2*(\\left\\lfloor\\dfrac{n}{2}\\right\\rfloor+1-f(\\left\\lfloor\\dfrac{n}{2}\\right\\rfloor))&amp;n&gt;1\\end{cases} f(n)={12∗(⌊2n​⌋+1−f(⌊2n​⌋))​n=1n&gt;1​ 由此，我们可以得到递归函数 int GreatPurge(int n) { if (n == 1) { return 1; // 初始条件 } else { return 2 * (n / 2 + 1 - GreatPurge(n / 2)); // 递归调用函数 } } 示例代码 #include &lt;stdio.h&gt; int GreatPurge(int); int main(void) { int n; scanf(&quot;%d&quot;, &amp;n); printf(&quot;%d&quot;, GreatPurge(n)); return 0; } int GreatPurge(int n) { if (n == 1) { return 1; // 初始条件 } else { return 2 * (n / 2 + 1 - GreatPurge(n / 2)); // 递归调用函数 } } L 见证奇迹的时刻 难度 考点 7~8 约瑟夫环、判断质数 题目分析 1. k−1.~k-1. k−约瑟夫环 k−k-k−约瑟夫环是指将 nnn 个物体编号 1,2,3,...,n1,2,3,...,n1,2,3,...,n 后首尾相连成一个环，从 111 号物体开始一次往后数，每数到第 kkk 个就将第 kkk 个移除，直到最后只剩一个。 设最后剩下的物体初始编号为 J(n)J(n)J(n)，则不难得到 J(1)=1J(1)=1J(1)=1 与递推式 J(n)=(J(n−1)+k)mod nJ(n)=(J(n-1)+k)\\mod n J(n)=(J(n−1)+k)modn 本题魔术最后一步即为一个 k=2k=2k=2 的约瑟夫环，在此条件下不难得到其通项公式为 J(n)=2(n−2⌊log⁡2n⌋)+1J(n)=2(n-2^{\\lfloor\\log_2n\\rfloor})+1 J(n)=2(n−2⌊log2​n⌋)+1 那么在倒数第二步中，就是要实现重复 QQQ 次将第一张牌放置最后的步骤后，使原来 2N−22N-22N−2 张牌中的最后一张牌，变为第 J(2N−2)J(2N-2)J(2N−2) 张牌；使原来 2N−32N-32N−3 张牌中的最后一张牌，变为第 J(2N−3)J(2N-3)J(2N−3) 张牌。令 QQQ 尽可能小时，也不难得到： Q(N)={2y−1 , 2y−1≤2N−3&lt;2N−2&lt;2y (1)22y−1 , 2N−2=2y (2)Q(N)= \\begin{cases} 2^y-1 &amp;~~~,~~~&amp; 2^{y-1} \\leq 2N-3 \\lt 2N-2 \\lt 2^y &amp;~~~(1)\\\\ 2^{2y}-1 &amp;~~~,~~~&amp; 2N-2=2^y &amp;~~~(2) \\end{cases}Q(N)={2y−122y−1​ , , ​2y−1≤2N−3&lt;2N−2&lt;2y2N−2=2y​ (1) (2)​ 也就是说，在 2N−2∈(2y−1,2y)2N-2\\in(2^{y-1},2^y)2N−2∈(2y−1,2y) 区间内，无论 NNN 具体为多少，QQQ 都恒定。也就是说，在已经出错（匹配牌在倒数第二张）的情况下，想成功就必须使 2N−M−1&lt;2y−12N-M-1&lt;2^{y-1}2N−M−1&lt;2y−1。 此时我们就能看出此题特殊输入方式的意义所在了，求 QQQ 的值我们并不关注 NNN 的具体值，我们只需要知道 2N−22N-22N−2 究竟落入了哪个 yyy 所对应的 (2y−1,2y)(2^{y-1},2^y)(2y−1,2y) 区间内。 2.2.2. 解同余方程 先考虑 (1)(1)(1) 的情况，设 2y−1≤2N−2&lt;2y2^{y-1} \\leq 2N-2 \\lt 2^y2y−1≤2N−2&lt;2y；2N−M−1=2x+z2N-M-1=2^x+z2N−M−1=2x+z，其中 z∈[0,2x)z\\in[0,2^x)z∈[0,2x)，且 zzz 为正整数。 魔术时需将第一张牌放最后的操作重复 Q(N)Q(N)Q(N) 次，而我们需要将第一张牌放最后的操作重复 2(2N−M−1)−1−J(2N−M−1)2(2N-M-1)-1-J(2N-M-1)2(2N−M−1)−1−J(2N−M−1) 次。 （其实大部分情况下只需要重复 2N−M−2−J(2N−M−N)2N-M-2-J(2N-M-N)2N−M−2−J(2N−M−N) 次，但这有可能是一个负数，并且实际重复次数一定超过我们需要次数的 222 倍，因此可以直接带入前者计算）。 那么现在，只要 QQQ 与我们所需操作次数的差值正好是 2N−M−12N-M-12N−M−1 的倍数，就能成功，也就是： Q(N)−2(2N−M−1)+1+J(2N−M−1)≡0mod (2N−M−1)Q(N)-2(2N-M-1)+1+J(2N-M-1)\\equiv 0 \\mod (2N-M-1) Q(N)−2(2N−M−1)+1+J(2N−M−1)≡0mod(2N−M−1) 将 x,y,zx,y,zx,y,z 代入得： 2y−2x+1+1≡0mod (2x+z)2^y-2^{x+1}+1 \\equiv 0 \\mod (2^x+z) 2y−2x+1+1≡0mod(2x+z) 设 y=x+py=x+py=x+p；2y−2x+1+1=a(2x+z)2^y-2^{x+1}+1=a(2^x+z)2y−2x+1+1=a(2x+z)，则有： 2x=az−12p−a−22^x=\\frac{az-1}{2^p-a-2} 2x=2p−a−2az−1​ zzz 的取值范围是 [0,2x)[0,2^x)[0,2x)，因此必须有 a2p−a−2&gt;1\\frac{a}{2^p-a-2} \\gt 12p−a−2a​&gt;1，也就是 a&gt;2p−1−1a \\gt 2^{p-1}-1a&gt;2p−1−1。 又因为 2x2^x2x 与 2p−a−22^p-a-22p−a−2 均为正偶数，因此 az−1az-1az−1 也必须为偶数，也就是 aaa 必须为奇数。 解得 a∈[2p−1+1，2p−3]a\\in[2^{p-1}+1，2^p-3]a∈[2p−1+1，2p−3]。 同时区间左端点 ≤\\leq≤ 区间右端点，解得 p≥3p \\geq 3p≥3。 3.3.3.“ 不会被发现 ” 此时我们可以进一步思考这个不等式了： ⌊Q2N−M−1⌋≤2⌊log⁡2(2N−2)⌋2⌊log⁡2(2N−M−1)⌋+2\\lfloor\\frac{Q}{2N-M-1}\\rfloor \\leq \\frac{2^{\\lfloor\\log_2(2N-2)\\rfloor}}{2^{\\lfloor\\log_2(2N-M-1)\\rfloor}}+2 ⌊2N−M−1Q​⌋≤2⌊log2​(2N−M−1)⌋2⌊log2​(2N−2)⌋​+2 此时我们可以看出左边式子的值为 aaa 或 a+1a+1a+1，右边式子的值为 2y−12x+2=2p−1+2\\frac{2^{y-1}}{2^x}+2=2^{p-1}+22x2y−1​+2=2p−1+2，也就是这个式子要使 aaa 或 a+1≤2p−1+2a+1 \\leq 2^{p-1}+2a+1≤2p−1+2，结合上一部分解出的 aaa 的范围与 aaa 为奇数的限制，可知 aaa 有且仅有一个取值： a=2p−1+1a=2^{p-1}+1 a=2p−1+1 4.4.4. 建立 yyy 与 ppp 的关系 至此，我们已经得到： 2x=(2p−1+1)z−12p−1−32^x=\\frac{(2^{p-1}+1)z-1}{2^{p-1}-3} 2x=2p−1−3(2p−1+1)z−1​ 值得注意的一点是，我们在解同余方程和求 aaa 的取值过程中一直采用的是充要条件推导，因此确定了 aaa 的值后就能保证 zzz 一定在其合法取值范围内。 那么此时，可将 zzz 视作倍数，转化成模 2p−1+12^{p-1}+12p−1+1 意义下的同余方程： (2p−1−3)⋅2x≡−1mod (2p−1+1)(2^{p-1}-3)·2^x \\equiv -1 \\mod (2^{p-1}+1) (2p−1−3)⋅2x≡−1mod(2p−1+1) 由 2p−1−3≡−4mod (2p−1+1)2^{p-1}-3 \\equiv -4 \\mod (2^{p-1}+1)2p−1−3≡−4mod(2p−1+1)可得： 2x+2≡1mod (2p−1+1)2^{x+2} \\equiv 1 \\mod (2^{p-1}+1) 2x+2≡1mod(2p−1+1) 由平方差公式可得，上式成立当且仅当： x+2=2k(p−1) , k=1,2,3,......x+2=2k(p-1)~~~,~~~k=1,2,3,...... x+2=2k(p−1) , k=1,2,3,...... 将 y=x+py=x+py=x+p 带入，解得： y+1=(2k+1)(p−1) , p≥3 , k=1,2,3,......y+1=(2k+1)(p-1) ~~~,~~~p \\geq 3~,~k=1,2,3,...... y+1=(2k+1)(p−1) , p≥3 , k=1,2,3,...... 由 kkk 与 ppp 的取值范围可知，在 (1)(1)(1) 条件下存在 MMM 的充要条件是 y+1y+1y+1 是合数，且具有除了 222 以外的质因子。 而在 (2)(2)(2) 条件下要将 yyy 变为 2y2y2y，并且 2y=x+p2y=x+p2y=x+p，即： 2y+1=(2k+1)(p−1)2y+1=(2k+1)(p-1) 2y+1=(2k+1)(p−1) 由 x&lt;yx \\lt yx&lt;y 知 p&gt;yp \\gt yp&gt;y，于是： k&lt;y+22y−2k \\lt \\frac{y+2}{2y-2} k&lt;2y−2y+2​ 当 N≥4N \\geq 4N≥4 时 y≥3y \\geq 3y≥3，易知 (2)(2)(2) 条件下任何情况均不成立。 至此我们能根据任意情况的 NNN 在 O(lnN)O(\\sqrt{lnN})O(lnN​) 的时间内，用类似于检验质数的方法判断是否存在 MMM 了。 示例代码 #include &lt;stdio.h&gt; char du[20000]; int check(int y) //判断是不是含有除了2以外质因子的合数 { int Y = y + 1; //别忘了要检验的是y+1而不是y if(Y % 2 == 0) { while(Y % 2 == 0) Y /= 2; if(Y != 1) return 1; } else { for(int i = 3; i * i &lt;= Y; i += 2) //如果此处不优化，O(lnN)会TLE if(Y % i == 0) return 1; } return 0; } int main() { int t, a, aa, flag, y, ans; while(scanf(&quot;%d&quot;, &amp;t) != EOF) { flag = 0; scanf(&quot;%d&quot;, &amp;a); if(t == 2) { scanf(&quot;%d&quot;, &amp;aa); if(aa == 0) { printf(&quot;NO\\n&quot;); //(2)的情况 continue; } ans = check(a + 2); //t=2的其它情况不要误判为(2)情况 } else if(t == 1) ans = check(a + 1); //此时y=a+1,常规情况下y=a+2 else //常规(1)情况 { gets(du); ans = check(a + 2); } if(ans) printf(&quot;YES\\n&quot;); else printf(&quot;NO\\n&quot;); } return 0; } - End - ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"E4 - Solution-23航C","link":"https://github.pansis.site/post/E4 - Solution-23航C/","stats":{"text":"43 min read","time":2532000,"words":9032,"minutes":43},"dateFormat":"2024-04-21"},{"content":"A 鸡兔同笼2024 难度 考点 1~2 解方程，分支结构 题目分析 本题只需要解二元一次方程 {x+y=n2x+4y=m\\left\\lbrace\\begin{matrix}x+y=n\\\\2x+4y=m\\end{matrix}\\right.{x+y=n2x+4y=m​ ，并判断解得的 x,yx,yx,y 是否满足以下两个要求： x,yx,yx,y 都是非负整数。 由于求解过程使用整除，需判断 x,yx,yx,y 带入原方程时是否能使等式成立。 只有满足以上两个条件，才算原方程有非负整数解。 示例代码 #include &lt;stdio.h&gt; int main() { int x, y, n, m; while (scanf(&quot;%d%d&quot;, &amp;n, &amp;m) != EOF) { x = (4 * n - m) / 2; y = n - x; if (m % 2 || x &lt; 0 || y &lt; 0) //判断解是否满足要求 printf(&quot;No answer\\n&quot;); else printf(&quot;%d %d\\n&quot;, x, y); } } B 这是什么三角形？ 难度 考点 2 分支结构 题目分析 为了方便判断，可以在判断之前将 aaa 设为 a,b,ca, b, ca,b,c​ 三者中的最大者。 优先判断特殊情况：无法组成三角形的情况和等边三角形的情况。 接下来通过 a2a^2a2 和 b2+c2b^2+c^2b2+c2​ 的大小关系判断三角形的基本形状；最后判断是否存在等腰性即可。 注意单词后的空格和换行符。 示例代码 #include &lt;stdio.h&gt; int main() { int a, b, c; while(scanf(&quot;%d %d %d&quot;, &amp;a, &amp;b, &amp;c) != EOF) { //保证a为三者中的最大者 if(b &gt; a) //若b比a大，交换a,b { int t = a; a = b; b = t; } if(c &gt; a) //若c比a大，交换a,c { int t = a; a = c; c = t; } //无法组成三角形 if(b + c &lt;= a) { printf(&quot;not a triangle\\n&quot;); continue; } //能组成三角形 if(a == b &amp;&amp; b == c) //先判断特殊情况：等边三角形 { printf(&quot;an equilateral triangle\\n&quot;); continue; } if(b * b + c * c &gt; a * a) //锐角三角形 printf(&quot;an acute &quot;); else if(b * b + c * c == a * a) //直角三角形 printf(&quot;a right &quot;); else printf(&quot;an obtuse &quot;); //钝角三角形 //判断等腰性 if(b == c || a == b || a == c) //存在等腰性 printf(&quot;isosceles &quot;); printf(&quot;triangle\\n&quot;); } return 0; } C czx 的戒指强化 难度 知识点 2 判断 题目分析 分析题目不难发现，实际上就是判断三个数是否在某个集合内。自然可以用 || 连接逻辑语句来逐一判断。然而，根据集合内元素的特征，也可以直接通过判断数字落在哪个区间内来解决。 示例代码 #include &lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;, &amp;n); while (n--) { int a, b, c; scanf(&quot;%d %d %d&quot;, &amp;a, &amp;b, &amp;c); int res = 0; if (a == 100) { res += 0; } else if (a == 150) { res += 1; } else { res += 2; } if (b &lt;= 30) { res += 0; } else if (b &lt;= 45) { res += 1; } else { res += 2; } if (c &lt;= 30) { res += 0; } else if (c &lt;= 45) { res += 1; } else { res += 2; } printf(&quot;%d\\n&quot;, res); } return 0; } D roy的军乐队排序 难度 考点 3 分支，循环，数组 题目分析 此题的整体思路较为简单，分为四步 使用三个数组存储三行士兵的高度 同时遍历三个数组，纵向排序 判断横向是否还有序 按格式输出三个数组 另外，printf很有很多强大的格式化输出的方法，感兴趣的同学可以参考C 库函数 – printf() | 菜鸟教程 (runoob.com) 最后，其实对一个横向有序的队列进行纵向排序后，一定不会影响它的横向有序性，即最后一定是输出true，发现了这点的同学可以直接跳过第三步，直接输出true。 注意大数组要开在 main 函数外面作为全局变量，此外请大家使用常量来定义数组长度，不推荐使用变长数组。 示例代码 1 #include &lt;stdio.h&gt; int flag; int a1[1000005]; int a2[1000005]; int a3[1000005]; int main() { // 第一步 读入士兵高度 int n; scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; i++) scanf(&quot;%d&quot;, &amp;a1[i]); for (int i = 0; i &lt; n; i++) scanf(&quot;%d&quot;, &amp;a2[i]); for (int i = 0; i &lt; n; i++) scanf(&quot;%d&quot;, &amp;a3[i]); // 第二步 纵向排序 for (int i = 0; i &lt; n; i++) { int temp; if (a1[i] &gt; a2[i]) {temp = a1[i]; a1[i] = a2[i]; a2[i] = temp;} if (a1[i] &gt; a3[i]) {temp = a1[i]; a1[i] = a3[i]; a3[i] = temp;} // 到这里之后 a1[i]一定是最大的 但a2[i]和a3[i]之间的大小关系不确定 if (a2[i] &gt; a3[i]) {temp = a2[i]; a2[i] = a3[i]; a3[i] = temp;} } // 第三步 判断横向是否还有序 for (int i = 0; i &lt; n - 1; i++) if (a1[i] &gt; a1[i + 1]) { flag = 1; break; } for (int i = 0; i &lt; n - 1; i++) if (a2[i] &gt; a2[i + 1]) { flag = 1; break; } for (int i = 0; i &lt; n - 1; i++) if (a3[i] &gt; a3[i + 1]) { flag = 1; break; } // 第四步 格式化输出三个数组 for (int i = 0; i &lt; n; i++) printf(&quot;%-7d&quot;, a1[i]); printf(&quot;\\n&quot;); for (int i = 0; i &lt; n; i++) printf(&quot;%-7d&quot;, a2[i]); printf(&quot;\\n&quot;); for (int i = 0; i &lt; n; i++)* printf(&quot;%-7d&quot;, a3[i]); printf(&quot;\\n&quot;); if (flag == 0) printf(&quot;true&quot;); else printf(&quot;false&quot;); } 示例代码 2 函数版本 我们可以将重复的功能（读入一个数组，输出一个数组，判断数组是否有序）包装为函数。此种做法可以显著提升代码的可读性，且debug更加方便，也不易出错。 也请同学们关注全局变量 （n,flag,a1,a2,a3）（n, flag, a1, a2, a3）（n,flag,a1,a2,a3）​ 和函数的配合使用，即函数中可以直接修改全局变量的值，减少了函数的传参，也让代码更加简洁。 #include &lt;stdio.h&gt; int n; int flag; int a1[1000005]; int a2[1000005]; int a3[1000005]; void read(int a[]) { for (int i = 0; i &lt; n; i++) scanf(&quot;%d&quot;, &amp;a[i]); } void print(int a[]) { for (int i = 0; i &lt; n; i++) printf(&quot;%-7d&quot;, a[i]); printf(&quot;\\n&quot;); } void swap(int a1[], int a2[], int i) { int temp = a1[i]; a1[i] = a2[i]; a2[i] = temp; } void sort(int i) { int temp; if (a1[i] &gt; a2[i]) swap(a1, a2, i); if (a1[i] &gt; a3[i]) swap(a1, a3, i); //到这里之后 a1[i]一定是最大的 但a2[i]和a3[i]之间的大小关系不确定 if (a2[i] &gt; a3[i]) swap(a2, a3, i); } void judge(int a[]) { for (int i = 0; i &lt; n - 1; i++) { if (a[i] &gt; a[i + 1]) { flag = 1; return; } } } int main() { // 第一步 读入士兵高度 scanf(&quot;%d&quot;, &amp;n); read(a1); read(a2); read(a3); // 第二步 纵向排序 for (int i = 0; i &lt; n; i++) sort(i); //第三步 判断横向是否还有序 judge(a1); judge(a2); judge(a3); //第四步 格式化输出三个数组 print(a1); print(a2); print(a3); if (flag == 0) printf(&quot;true&quot;); else printf(&quot;false&quot;); } E czx 的问答游戏 难度 知识点 3 循环，条件语句 题目分析 不难发现，要解决这个问题，可以分两步来解决： 找到满足条件（ai≤10a_i \\leq 10ai​≤10）的 bib_ibi​ 最大值 maxbmaxbmaxb； 找到所有满足 ai≤10a_i \\leq 10ai​≤10 且 bi=maxbb_i = maxbbi​=maxb 的 iii. 用两个循环即可解决这个问题。第一个循环寻找最大值，并用一个变量来实时记录更新；第二个循环寻找所有满足条件的编号，并输出。 示例代码 1 #include &lt;stdio.h&gt; #define N 1005 int num, res[N], a[N], b[N]; int main() { int t; scanf(&quot;%d&quot;, &amp;t); while (t--) { int n, maxx = -1; scanf(&quot;%d&quot;, &amp;n); for (int i = 1; i &lt;= n; i++) { scanf(&quot;%d %d&quot;, &amp;a[i], &amp;b[i]); if (a[i] &lt;= 10 &amp;&amp; b[i] &gt; maxx) { maxx = b[i]; } } num = 0; for (int i = 1; i &lt;= n; i++) { if (a[i] &lt;= 10 &amp;&amp; b[i] == maxx) { res[num++] = i; } } printf(&quot;%d\\n&quot;, num); for (int i = 0; i &lt; num; i++) { printf(&quot;%d &quot;, res[i]); } printf(&quot;\\n&quot;); // 注意最后换行 } return 0; } 示例代码 2 与代码一思路类似，找最大值和记录最大值出现位置写在了同一个循环里。 #include&lt;stdio.h&gt; int main() { int t; scanf(&quot;%d&quot;, &amp;t); while(t--) { int n, k = 0, id[505], max = 0; scanf(&quot;%d&quot;, &amp;n); for(int i = 1; i &lt;= n; ++i) { int a, b; scanf(&quot;%d%d&quot;, &amp;a, &amp;b); if(a &lt;= 10) { if(b == max) id[k++] = i; else if(b &gt; max) { max = b; k = 0; id[k++] = i; } } } printf(&quot;%d\\n&quot;, k); for(int i = 0; i &lt; k; ++i) printf(&quot;%d &quot;, id[i]); printf(&quot;\\n&quot;); } return 0; } F 匹配星期 难度 考点 3~4 结构化编程，ZellerZellerZeller 公式 题目分析 ZellerZellerZeller 公式： w=(⌊c4⌋−2c+y+⌊y4⌋+⌊13(m+1)5⌋+d−1) mod 7w=(\\lfloor\\frac{c}{4}\\rfloor-2c+y+\\lfloor\\frac{y}{4}\\rfloor+\\lfloor\\frac{13(m +1)}{5}\\rfloor+d-1)\\bmod7 w=(⌊4c​⌋−2c+y+⌊4y​⌋+⌊513(m+1)​⌋+d−1)mod7 其中 www 为星期；ccc 为年的前两位；yyy 为年的后两位；mmm 为月；ddd 为日。 别忘记每年的 1、21、21、2 月要当做前一年的 13、1413、1413、14 月来计算。 示例代码 #include&lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;, &amp;n); for(int i = 1; i &lt;= n; i++) { int yyyy, mm, dd, w; scanf(&quot;%d.%d.%d%d&quot;, &amp;yyyy, &amp;mm, &amp;dd, &amp;w); if (mm &lt;= 2) { yyyy--; mm += 12; } int c = yyyy / 100; int y = yyyy % 100; if((c / 4 - 2 * c + y + y / 4 + 13 * (mm + 1) / 5 + dd - 1 - w) % 7 == 0) printf(&quot;YES\\n&quot;); else printf(&quot;NO\\n&quot;); } return 0; } 思考一下：为什么星期的判断写成 if((......-w)%7==0) 而不是 if(......%7==w) 呢？ 不妨让计算机输出一下 (-5)/3,(-5)/(-3),5/(-3),(-5)%3,(-5)%(-3),5%(-3) 的值，看看和你想的有没有差距？ 这是因为 ZellerZellerZeller 公式对 777 取模前的数有可能是一个负数，CCC 语言中的整除是 ⌈\\lceil⌈ 对 000 取整 ⌋\\rfloor⌋，具体表现为结果为正数时向下取整，结果为负时向上取整，并且取模结果与整除结果相一致，也就是说 (a-a/b*b)==(a%b) 恒成立。 或者我们可以理解成：CCC 语言中的 a%b 和 a/b 都是先对 a 和 b 取绝对值运算，再考虑符号正负。a%b 符号与 a 一致；a/b 是 “ 同号为正，异号为负 ”。 回到这道题里也就是说，如果取模前 ZellerZellerZeller 公式的结果是一个负数，取模后一定也不是正数（可能是 000），因此写成第一种才正确。 G 判断缓存是否命中 难度 考点 4 模拟，循环 大致题意 给定缓存大小和一段缓存序列，使用最近最久未使用（LRU）策略判断每个缓存请求是否命中。 题目分析 这个题目的信息量很大，如果是做题的话，可以忽略背景信息，直接阅读题目描述。 根据题目的要求，我们可以用一个一维数组来模拟缓存（大小应该比缓存大小稍大一些，避免数组越界），将下标 1,2,⋯N1,2,\\cdots N1,2,⋯N 作为缓存有效的存储位置。初始时将缓存中的地址都填充为无效的地址（-1）。每当一个请求到来的时候，我们首先遍历数组，查找请求地址是否在数组中。如果地址在数组中，则输出 YES {该地址在数组中的位置}，然后将该地址移到数组的首部，将原来排在该地址前面的地址依次向后移动一位。如果地址不在缓存中，则输出 NO，然后在数组的首部插入该地址，数组中的其他地址依次向后移动一位。原来位于数组中最后一个位置的地址将被移出数组，从而被丢弃。 示例代码 1 #include&lt;stdio.h&gt; #define N 100005 int arr[N]; int main() { int n, l; int loc = -1; scanf(&quot;%d%d&quot;, &amp;n, &amp;l); for (int i = 1; i &lt;= n; ++i) { arr[i] = -1; // 将数组下标 1-n 存储的地址设置为无效地址，注意数组的下标 0（第一个元素）未被使用 } for (int i = 1; i &lt;= l; ++i) { int addr; scanf(&quot;%d&quot;, &amp;addr); // 读入一个新的地址 int flag = 0; int loc; for (int j = 1; j &lt;= n; ++j) { if (arr[j] == addr) { // 如果缓存中有该地址 flag = 1; // 设置命中标记 loc = j; for (int k = j - 1; k &gt;= 1; --k) { arr[k + 1] = arr[k]; // 排在该地址的前面的地址依次向后移动一位，注意应该从下标较大的地址开始移动，否则 } // 下标较大的地址将被下标较小的地址覆盖！！！ arr[1] = addr; // 将命中的地址移动到数组下标 1 对应的位置。 break; } } if (flag) { // 如果命中 printf(&quot;YES %d\\n&quot;, loc); } else { printf(&quot;NO\\n&quot;); } if (!flag) { // 未命中，将原来在缓存中的地址依次向右移动一位，仍然是从下标较大的地址开始移动，注意原来下标为 n 的地址被丢弃 for (int j = n - 1; j &gt;= 1; --j) { arr[j + 1] = arr[j]; } arr[1] = addr; // 将未命中的地址放置到数组下标 1 对应的位置 } } return 0; } 示例代码 2 宋老师的版本，使用了函数简化代码。 #include &lt;stdio.h&gt; // 第四步：把函数原型补充上 int lookupX(int); int sizeN(void); void insertX(int, int); int cacheN[1005]; //数组长度定义稍微多几个 int N, L; // 分析的第一步：画图分析样例，给出求解思路，这步很关键 // 分析的第二步：设计算法，因为第一部的基础已经很好，我就直接写在主函数main中 int main() { int i, x; scanf(&quot;%d%d&quot;, &amp;N, &amp;L); for(i=1; i&lt;=N; i++) cacheN[i] = -1; //缓存里每一个位置初始化为-1，表示初始状态下没有存储任何地址 for(i=1; i&lt;=L; i++) { scanf(&quot;%d&quot;, &amp;x); int pos = lookupX(x); // 在cacheN里查找x是否存在，存在返回位置，不存在返回0 // cacheN是全局数组，可以在任意地方访问，不需要作为参数 int size_N = sizeN(); // 计算cacheN里存有多少个地址 if(pos == 0) { printf(&quot;NO\\n&quot;); insertX(x, size_N+1); // cacheN里地址有size_N个，把地址x添加到缓存第1位，前size_N个地址依序后移1个位置，函数执行完后缓存里地址有size_N+1个（如果越界，也没有关系，越界的值不会用） // 系统学习函数和数组后，可以看到，这样设计函数并不是很好。需要访问的数组（地址）也应该作为函数参数，让函数模块化特征更好。 } else { printf(&quot;YES %d\\n&quot;, pos); insertX(x, pos); // 把地址x（在位置pos处）添加到缓存第1位，前pos-1个地址依序后移1个位置，pos+1后面的地址不用变 } //for test, 第五步，样例数据错误，开始debug，输出中间结果，分析误地方，这步最艰难 /* for(int j=1; j&lt;N; j++) { printf(&quot; %d&quot;, cacheN[j]); } printf(&quot;\\n&quot;); */ // 每次请求后，把缓存里的数据都输出，对照样例数据（如果样例数据简单，自己构造更通用的数据），分析自己的错误出现在哪一步，逐步排查，发现一处错误，改进一处，把错误逐步消亡。 // 排查完所有错误并改正后，结果正确。最后需要把这些输出中间结果的代码都删除，或注释掉。 } return 0; } // 第三步，各个函数（子问题）的独立设计 int lookupX(int x) { int pos = 0; for(int i=1; i&lt;=N &amp;&amp; cacheN[i] != -1; i++) { if(x == cacheN[i]) { pos = i; break; } } return pos; } int sizeN() { int i; for(i=1; i&lt;=N; i++) { if(cacheN[i] == -1) break; } return i-1; } /* 后记 效率可以更高些，如下： 请求N次前，顺序查找，如示例。 请求N次后，倒序查找，缓存中的地址不等于-1就结束循环。因为请求若干次后，数组通常已经满了，倒序查找第一次就结束了，自然效率高。 事实上，size_N是非减增长的，缓存满了后， size_N=N保持不变，后面的请求中，缓存都是满的状态，无需再计算sizeN这个函数。 追求，永无止境！ */ // 前pos-1个地址依序后移1个位置，把地址x添加到缓存第1位，pos+1后面的地址不用变 void insertX(int x, int pos) { for(int i=pos; i&gt;=2; i--) cacheN[i] = cacheN[i-1]; cacheN[1] = x; } H 小霁的时间乱流plus++ 难度 考点 4~5 进制转换 题目分析 本题在分钟进位小时，小时进位天数较为简单。 但在天数进位月份时，需要考虑每月天数不同也需要考虑闰年的因素。 闰年的规定是 “能被400整除，或者能被4整除但不能被100整除的是闰年”，转化为C语言就是((y % 4 == 0 &amp;&amp; y % 100 != 0) || y % 400 == 0)，如果该条件式成立，则y是闰年，反之不是闰年。 具体的乱流中的时间调整方式已在题目中详细给出，此处不再赘述。 示例代码 #include&lt;stdio.h&gt; int main() { int months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //定义每月天数 int n;//数据组数 int y, mon, d, h, min; //年、月、日、时、分 int tempday; scanf(&quot;%d&quot;, &amp;n); for (int i = 0; i &lt; n; ++i) { scanf(&quot;%d.%d.%d %d:%d&quot;, &amp;y, &amp;mon, &amp;d, &amp;h, &amp;min); //输入乱流中的时间 h += (min / 60); min %= 60; d += (h / 24); h %= 24; //计算正常的小时数、分钟数，并向天数进位 y += (mon - 1) / 12; mon = (mon - 1) % 12 + 1;//先将月份向年份进位 tempday = months[mon] + (((y % 4 == 0 &amp;&amp; y % 100 != 0) || y % 400 == 0) &amp;&amp; (mon == 2)); //计算当前的y和mon对应的当月天数 //(((y % 4 == 0 &amp;&amp; y % 100 != 0) || y % 400 == 0)&amp;&amp;(mon==2))用于判定当前的y和mon是否是闰年的2月，若条件式成立，则条件式的值为1，在28天的基础上加1天。 while (d &gt; tempday) //如果天数超过该月份的天数 { d -= tempday; mon++;//天数减去该月天数，月份加一 y += (mon - 1) / 12; mon = (mon - 1) % 12 + 1;//若月份大于12，则向年份进位 tempday = months[mon] + (((y % 4 == 0 &amp;&amp; y % 100 != 0) || y % 400 == 0) &amp;&amp; (mon == 2)); //计算当前的y和mon对应的当月天数 } printf(&quot;%04d.%02d.%02d %02d:%02d\\n&quot;, y, mon, d, h, min); } return 0; } I 对称的 2k+1 进制 2024 难度 考点 5 平衡进制 题目分析 可以先将数码存在一个字符数组中等待调用。对于平衡 2k+12k+12k+1 进制，可以先将其转换为普通的 2k+12k+12k+1 进制，再进行转换。 注意到普通进制的数码是 000 到 2k2k2k，平衡进制的数码是 −k-k−k 到 kkk 。若一个数在转换前后的数码都在 000 至 kkk 内，则无需转换，因此转换的主要对象是大于 kkk 的数码。对于普通 2k+12k+12k+1 进制的数码 m,m&gt;km,m&gt;km,m&gt;k，可以将这一位数转换为平衡进制数码 m−(2k+1)m-(2k+1)m−(2k+1) 并且令高位增加 111，保持相等。对于整数内的运算，则可以类比，使用“整数位本身减 2k+12k+12k+1，高位进一”的方法，这样就实现了转换。 注意到平衡进制正负数只要数码取反即可，因此可将负数先化为正数，再逐位取相反数即可。 示例代码 1 #include&lt;stdio.h&gt; char s[20] = {'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; // char s[20] = &quot;IHGFEDCBA0123456789&quot;; int ans[20], cnt; int main() { int n, k; scanf(&quot;%d%d&quot;, &amp;n, &amp;k); if (n == 0) { printf(&quot;0\\n&quot;); } else if (n &gt; 0) { while (n &gt; 0) { ans[cnt] = n % (2 * k + 1); n = n / (2 * k + 1); cnt++; } for (int i = 0; i &lt;= cnt - 1; i++) { if (ans[i] &gt;= k + 1) { ans[i] -= (2 * k + 1); ans[i + 1]++; if (i == cnt - 1)cnt++; } } for (int i = cnt - 1; i &gt;= 0; i--) { printf(&quot;%c&quot;, s[9 + ans[i]]); } } else if (n &lt; 0) { n = -n; while (n &gt; 0) { ans[cnt] = n % (2 * k + 1); n = n / (2 * k + 1); cnt++; } for (int i = 0; i &lt;= cnt - 1; i++) { if (ans[i] &gt;= k + 1) { ans[i] -= (2 * k + 1); ans[i + 1]++; if (i == cnt - 1)cnt++; } } for (int i = cnt - 1; i &gt;= 0; i--) { printf(&quot;%c&quot;, s[9 - ans[i]]); } } return 0; } 示例代码 2 推荐下节课学习了函数与递归之后再来回顾本题的本解法。 设 nnn 的平衡 2k+12k+12k+1 进制的最低位为 mmm，则 n−m2k+1\\dfrac{n-m}{2k+1}2k+1n−m​ 的平衡 2k+12k+12k+1 进制与 mmm 拼接后就是 nnn 的平衡 2k+12k+12k+1 进制表示。 例如 −17-17−17 的平衡 333 进制的最低位为 111，−17−13=−6\\dfrac{-17-1}{3}=-63−17−1​=−6 的平衡 333 进制表示为 A10，−17-17−17 的平衡 333 进制表示为 A101。 可以使用递归函数的做法解决本题。 #include &lt;stdio.h&gt; void f(int n, int k) //函数功能：输出n的平衡k进制表示 { int m = (n % k + k) % k; //求出普通k进制的最低位 if(m &gt; k / 2) m -= k; //求出平衡k进制的最低位 //上面可以写作 int m = (n % k + k + k / 2) % k - k / 2; if(n - m) //若(n-m)/k不为0，说明高位还没有计算完 f((n - m) / k, k); //递归输出(n-m)/k的平衡2k+1进制表示 printf(&quot;%c&quot;, m &lt; 0 ? 'A' - m - 1 : '0' + m); //输出n的平衡k进制的最低位 } int main() { int n, k; scanf(&quot;%d%d&quot;, &amp;n, &amp;k); f(n, 2 * k + 1); return 0; } J 魅力四射 难度 考点 6 贪心 题目分析 ​ 首先，我们可以看出有区间改变后的答案一定是不降的。 ​ 另外，由简单贪心，我们可以知道，选取的区间的左右端点处的数一定被统计到答案里。 ​ 如果我们用 res[i] 数组来记录位置 1∼i1 \\sim i1∼i 中，cntx−cntx+kcnt_ {x} - cnt _ {x + k}cntx​−cntx+k​ 的最大值（此处记录答案相当于为区间加 kkk 后为 x+kx + kx+k），那么对答案取 maxmaxmax 一定不劣且不会漏解。 ​ 再通过一个 sum 数组记录每个数在数组中最开始出现的次数，那么答案是 max∗{sum[x+k]+res[x+k]}max ^ {*} \\lbrace sum[x + k] + res[x + k] \\rbrace max∗{sum[x+k]+res[x+k]} 示例代码 #include &lt;stdio.h&gt; #include &lt;string.h&gt; #include &lt;math.h&gt; #include &lt;stdlib.h&gt; #define max(a, b) (((a) &gt; (b)) ? (a) : (b)) #define maxn 4000005 #define N 2000000 int arr[maxn]; int sum[maxn], res[maxn]; int n, k, ans; int main() { scanf(&quot;%d %d&quot;, &amp;n, &amp;k); for(int i = 1; i &lt;= n; i++) { scanf(&quot;%d&quot;, &amp;arr[i]); sum[arr[i] + N]++; ans = max(ans, sum[arr[i] + N]); } for(int i = 1; i &lt;= n; i++) { res[arr[i] + k + N]++; if(res[arr[i] + N]) res[arr[i] + N]--; ans = max(ans, sum[arr[i] + k + N] + res[arr[i] + k + N]); } printf(&quot;%d\\n&quot;, ans); return 0; } - End - ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"E3 - Solution-23航c","link":"https://github.pansis.site/post/e3-solution-23-hang-c/","stats":{"text":"26 min read","time":1548000,"words":5669,"minutes":26},"dateFormat":"2024-03-28"},{"content":"A czx 的柠檬可乐 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main(){ int a,b,k; while (scanf(&quot;%d%d%d&quot;,&amp;a,&amp;b,&amp;k)!=EOF) { if (a&gt;=k*b) printf(&quot;g00d\\n&quot;); else printf(&quot;bad\\n&quot;); } } B 从十进制数到8421码 #include&lt;stdio.h&gt; int main() { int n; while(~scanf(&quot;%1d&quot;, &amp;n)) printf(&quot;%d%d%d%d&quot;, n &gt;&gt; 3 &amp; 1, n &gt;&gt; 2 &amp; 1, n &gt;&gt; 1 &amp; 1, n &amp; 1); return 0; } #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main(){ char c; while ((scanf(&quot;%c&quot;,&amp;c))!=EOF) { switch (c) { case '0': { printf(&quot;0000&quot;); break; } case '1': { printf(&quot;0001&quot;); break; } case '2': { printf(&quot;0010&quot;); break; } case '3': { printf(&quot;0011&quot;); break; } case '4': { printf(&quot;0100&quot;); break; } case '5': { printf(&quot;0101&quot;); break; } case '6': { printf(&quot;0110&quot;); break; } case '7': { printf(&quot;0111&quot;); break; }case '8': { printf(&quot;1000&quot;); break; } case '9': { printf(&quot;1001&quot;); break; } } } } C 小蓝蓝吃蛋糕 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main(){ int n,t,s; scanf(&quot;%d%d%d&quot;,&amp;n,&amp;t,&amp;s); if (t==0) { printf(&quot;0&quot;); return 0; } if (s%t==0) { n-=(s/t); } else n-=(s/t+1); if (n&lt;0) printf(&quot;0&quot;); else printf(&quot;%d&quot;,n); } D 判断互质2024 #include &lt;stdio.h&gt; #include &lt;math.h&gt; long long mian123(long long a,long long b) { if (b==0) return (a&gt;0)?a:-a; return mian123(b,a%b); } int main(){ long long l,r,x; scanf(&quot;%lld%lld%lld&quot;,&amp;l,&amp;r,&amp;x); int num=0; for (long long i = l; i &lt;=r ; ++i) { if (mian123(i,x)==1) num++; } printf(&quot;%d&quot;,num); } E 伪随机码生成 #include &lt;stdio.h&gt; int main() { int t; scanf(&quot;%d&quot;,&amp;t); int a[10]={0}; for (int i = 0; i &lt; 4; ++i) { scanf(&quot;%d&quot;,&amp;a[i]); } while (t--) { int f=a[1]^a[3]; printf(&quot;%d&quot;,a[3]); a[3]=a[2]; a[2]=a[1]; a[1]=a[0]; a[0]=f; } } #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main(){ int t; scanf(&quot;%d&quot;,&amp;t); int a=0,b; for (int i = 0; i &lt; 4; ++i) { scanf(&quot;%d&quot;,&amp;b); a|=(b&lt;&lt;i); } while (t--) { int f=((a&gt;&gt;1)&amp;1)^((a&gt;&gt;3)&amp;1); printf(&quot;%d&quot;,(a&gt;&gt;3)&amp;1); a&lt;&lt;=1; a|=f; } } F 哦，夏天！ #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main() { int n; double t[105]; while ((scanf(&quot;%d&quot;,&amp;n))!=EOF) { int flag=0; for (int i = 0; i &lt; n; ++i) { scanf(&quot;%lf&quot;,&amp;t[i]); } int big_day_num=0; for (int i = 4; i &lt; n; ++i) { double sum=0; for (int j = 0 ; j &lt; 5; ++j) { sum+=t[i-j]; } if (sum&gt;22*5) big_day_num++; else big_day_num=0; if (big_day_num==5) { for (int j = i-8; j &lt;n ; ++j) { if (t[j]&gt;22) { printf(&quot;Success %d\\n&quot;,j+1); flag=1; break; } } } if (flag==1) break; } if (flag==0) printf(&quot;Failure\\n&quot;); } } G Firefly小姐的彩灯 /* Author: 潘宇恒(49720) Result: AC Submission_id: 6047449 Created at: Tue Mar 19 2024 22:34:35 GMT+0800 (China Standard Time) Problem: 7693 Time: 5 Memory: 1628 */ //sit=0 从未亮过 //1 正在亮，且下一轮还亮 //-1 正在亮，但马上熄灭的 //2 没亮，但马上亮的 //-2 亮过且熄灭的 #include&lt;stdio.h&gt; int main() { int n; int s[105]={0}; int sit[105]={0}; int k; scanf(&quot;%d&quot;,&amp;n); for (int i = 1; i &lt;= n; ++i) { scanf(&quot;%d&quot;,&amp;s[i]); } scanf(&quot;%d&quot;,&amp;k); int liang_num=1; sit[k]=1; s[k]++; while (liang_num!=0) { for (int i = 1; i &lt;=n ; ++i) { if (sit[i]==1) { s[i]--; if (s[i]==0) { sit[i]=-1; liang_num--; } else printf(&quot;%d &quot;,i); } if (sit[i]==0) { if (sit[i-1]==1 ||sit[i-1]==-1||sit[i+1]==1) { if (s[i]!=0) { sit[i]=2; liang_num++; printf(&quot;%d &quot;,i); } } } } for (int i = 1; i &lt;=n ; ++i) { if (sit[i]==-1) sit[i]=-2; if (sit[i]==2) sit[i]=1; } printf(&quot;\\n&quot;); } } ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"C3讲解-23航c","link":"https://github.pansis.site/post/c3-jiang-jie-23-hang-c/","stats":{"text":"6 min read","time":301000,"words":838,"minutes":6},"dateFormat":"2024-03-22"},{"content":"A czx 的柠檬可乐 难度 知识点 1 循环 题目分析 不定组输入使用 while(~scanf(...)) 来完成，每组的判断直接用最简单的 if-else 语句实现即可（也可以用三目运算符）。每组数据输出后注意需要换行。 示例代码 #include &lt;stdio.h&gt; int main() { int a, b, k; while (~scanf(&quot;%d%d%d&quot;, &amp;a, &amp;b, &amp;k)) { printf(a &gt;= k * b ? &quot;g00d\\n&quot; : &quot;bad\\n&quot;); } return 0; } B 从十进制数到8421码 难度 考点 1~2 位运算、if-else、switch 题目分析 8421码对应的恰好是这一位数的二进制表示，因此可以直接通过位运算，完成8421码的处理。 注意：每次读入一位数可使用控制符 &quot;%1d&quot; ，也可用 &quot;%c&quot; 或 getchar() 完成输入，但要注意自己输入的数据类型。 示例代码 1 #include&lt;stdio.h&gt; int main() { int n; while (scanf(&quot;%1d&quot;, &amp;n) != EOF) { for (int i = 3; i &gt;= 0; i--) { printf(&quot;%d&quot;, n &gt;&gt; i &amp; 1); } } return 0; } 示例代码 2 #include&lt;stdio.h&gt; int main() { char n; while (scanf(&quot;%c&quot;, &amp;n) != EOF) //while ((n = getchar()) != EOF) 也可完成，效果相同 { for (int i = 3; i &gt;= 0; i--) { printf(&quot;%d&quot;, (n - '0') &gt;&gt; i &amp; 1);//为什么要减'0'？ } } return 0; } 扩展阅读 如果我没能观察出这个运算性质，该怎么办呢？其实，也可通过10个并列的if-else语句或switch语句完成。 示例代码 3 //采用if-else结构 #include&lt;stdio.h&gt; int main() { char ch; while (scanf(&quot;%c&quot;, &amp;ch) != EOF) { if (ch == '0') printf(&quot;0000&quot;); else if (ch == '1') printf(&quot;0001&quot;); else if (ch == '2') printf(&quot;0010&quot;); else if (ch == '3') printf(&quot;0011&quot;); else if (ch == '4') printf(&quot;0100&quot;); else if (ch == '5') printf(&quot;0101&quot;); else if (ch == '6') printf(&quot;0110&quot;); else if (ch == '7') printf(&quot;0111&quot;); else if (ch == '8') printf(&quot;1000&quot;); else if (ch == '9') printf(&quot;1001&quot;); } return 0; } 示例代码 4 //采用switch结构 #include &lt;stdio.h&gt; int main() { int n; while (scanf(&quot;%1d&quot;, &amp;n) != EOF) //也可使用char c;scanf(&quot;%c&quot;, &amp;c)或c=getchar()的结构 //使用字符输入时，注意后面的0-9要对应改成'0'-'9' { switch (n) { case 0: printf(&quot;0000&quot;); break; case 1: printf(&quot;0001&quot;); break; case 2: printf(&quot;0010&quot;); break; case 3: printf(&quot;0011&quot;); break; case 4: printf(&quot;0100&quot;); break; case 5: printf(&quot;0101&quot;); break; case 6: printf(&quot;0110&quot;); break; case 7: printf(&quot;0111&quot;); break; case 8: printf(&quot;1000&quot;); break; case 9: printf(&quot;1001&quot;); break; } } return 0; } C 小蓝蓝吃蛋糕 难度 考点 2 分支判断，向上取整 题目分析 本题主要考察分支判断和向上取整的有关知识，注意数据为0时的特判。 首先，题干中的每个数据都给出了数据范围，都不大，所以可以放心用int。 题干要求输入3个非负整数n,t,sn,t,sn,t,s，分别代表蛋糕总数，吃一个蛋糕所用时间，经过的时间。最后需要我们计算出经过sss分钟后还剩多少个完整的蛋糕。 如果t==0，那么意味着小蓝蓝能一口气吃下所有蛋糕，无论经过多少时间，答案均为0。 如果t!=0，则可以先计算出在这sss分钟内能吃多少个蛋糕，注意向上取整，然后分为两种情况： 吃的比原来的多，则答案为0。 吃的比原来的少，输出剩下的蛋糕的数量。 计算正整数除法 a/ba/ba/b 向上取整 ⌈ab⌉\\left\\lceil\\dfrac ab\\right\\rceil⌈ba​⌉ ，可以使用代码 (a + b - 1) / b。 示例代码 1 #include &lt;stdio.h&gt; int main() { int n, t, s, eat; scanf(&quot;%d%d%d&quot;, &amp;n, &amp;t, &amp;s); if (t == 0) { printf(&quot;0&quot;); } else { eat = (s + t - 1) / t; // s/t向上取整 if (eat &lt; n) { printf(&quot;%d&quot;, n - eat); } else { printf(&quot;0&quot;); } } return 0; } 示例代码 2 若 s&gt;n×ts&gt;n\\times ts&gt;n×t，则应该输出 000； 否则，（此时一定有 t&gt;0t&gt;0t&gt;0）直接计算 nnn 减去 ⌈st⌉\\left\\lceil\\dfrac st\\right\\rceil⌈ts​⌉ 即可。 #include&lt;stdio.h&gt; int main() { int n, t, s; scanf(&quot;%d%d%d&quot;, &amp;n, &amp;t, &amp;s); printf(&quot;%d&quot;, s &gt; n * t ? 0 : n - (s + t - 1) / t); return 0; } D 判断互质2024 难度 知识点 3 辗转相除法 题目分析 循环枚举 [l,r][l, r][l,r] 内的每一个数，逐一判断是否和 xxx 互质即可。 判断互质可以转化为求两个数的最大公因数，如果最大公因数为 111 则说明互质。求两个数的最大公因数可以用辗转相除法实现。 辗转相除法，也称为欧几里德算法，是一种用于计算两个整数的最大公约数的算法。其基本思想是通过反复地用较小数去除较大数得到余数，然后用上一步的除数作为被除数，余数作为除数，依次重复这个过程，直到余数为 000 为止，此时最后一次的除数即为两个整数的最大公约数。 具体过程如下： 将两个整数 a 和 b 中较大的数赋值给 a，较小的数赋值给 b。 用 b 去除 a，得到余数 remainder。 将 b 赋值给 a，将 remainder 赋值给 b。 重复以上步骤，直到 b 等于0。 当 b 等于0 时，a 即为两个整数的最大公约数。 通过这种方法，可以快速有效地找到两个整数的最大公约数。 示例代码 1 #include &lt;stdio.h&gt; int main() { long long l, r; int x, cnt = 0; scanf(&quot;%lld%lld%d&quot;, &amp;l, &amp;r, &amp;x); for(long long i = l; i &lt;= r; i++) { long long a = x, b = i; while(b) { long long t = a % b; a = b; b = t; } //此时a就是x与i的最大公约数 if(a == 1) cnt++; } printf(&quot;%d&quot;, cnt); return 0; } 示例代码 2 #include &lt;stdio.h&gt; int main() { long long l, r; int x, cnt = 0; scanf(&quot;%lld%lld%d&quot;, &amp;l, &amp;r, &amp;x); for(long long i = l; i &lt;= r; i++) { int a = x, b = i % x; //由于x在int范围内，故i%x一定也在int范围内 while(b) { int t = a % b; a = b; b = t; } cnt += a == 1; //等价于if(a == 1) cnt++; } printf(&quot;%d&quot;, cnt); return 0; } E 伪随机码生成 难度 考点 3 循环 位运算 题目分析 这道题对于移位寄存器的存储的 1bit1bit1bit 大小的值有两种表示，一种是每一个寄存器直接用一个 int 类型的变量表示，一共用 4 个 int 类型变量，如示例代码 111 。另外一种是利用一个变量的每一位表示，仅需一个 int 类型变量，如示例代码 222。 需要注意的是寄存器中的值的时序问题，反馈值要在寄存器的值变化之前计算出来，而且寄存器要从 a3a_3a3​ 开始向前变化，最后给 a0a_0a0​ 赋值为反馈值，或者直接采用示例代码 222 的统一移位。 示例代码 1 #include &lt;stdio.h&gt; int main() { int t; scanf(&quot;%d&quot;, &amp;t); int a[10] = {0}; for (int i = 0; i &lt; 4; ++i) { //读取初始状态 scanf(&quot;%d&quot;, &amp;a[i]); } while (t--) { //t次循环 int f = a[1] ^ a[3]; //反馈值提前计算出来 printf(&quot;%d&quot;, a[3]); //打印输出值 //更改寄存器的值（相当于移位） for (int i = 3; i &gt;= 1; --i) { a[i] = a[i - 1]; } a[0] = f; } } 示例代码 2 #include &lt;stdio.h&gt; int main() { int t; scanf(&quot;%d&quot;, &amp;t); int a = 0, b; for (int i = 0; i &lt; 4; ++i) { scanf(&quot;%d&quot;, &amp;b); a |= (b &lt;&lt; i); } while (t--) { //t次循环 int f = ((a &gt;&gt; 1) &amp; 1) ^ ((a &gt;&gt; 3) &amp; 1); //反馈值 printf(&quot;%d&quot;, (a &gt;&gt; 3) &amp; 1); a &lt;&lt;= 1;//统一移位 a |= f; } return 0; } F 哦，夏天！ 难度 考点 4 模拟、结构化编程 题目分析 模拟该过程即可，从第 555 天开始计算每一天的滑动均值。以下方代码为例，当滑动均值大于 222222 的时候， cnt 计数，小于等于 222222 的时候则重新置零。当 cnt 等于 555 的时候，从先前的第 888 天开始回溯，找出大于 222222 的第一天，找出后标记结果并跳出即可. 示例代码 1 #include&lt;stdio.h&gt; int main() { int n; double da[101], ds[101]; while(scanf(&quot;%d&quot;, &amp;n) != EOF) { int cnt = 0, day = 0; for(int i = 1; i &lt;= n; i++) { scanf(&quot;%lf&quot;, &amp;da[i]); } for(int i = 5; i &lt;= n; i++) { if(day != 0)break; //day标记前面是否已经出现了符合题意的情况 ds[i] = (da[i - 4] + da[i - 3] + da[i - 2] + da[i - 1] + da[i]) / 5; //计算滑动均值 if(ds[i] &gt; 22)cnt++; //滑动均值大于22，则统计 else cnt = 0; //出现小于22的，退出清零，重新统计 if(cnt == 5) //满足连续5天条件 { for(int j = i - 8; j &lt;= i - 4; j++) //开始回溯 { if(da[j] &gt; 22) //找到结果 { day = j; break; //标记并跳出 } } } } if(day != 0) printf(&quot;Success %d\\n&quot;, day); else printf(&quot;Failure\\n&quot;); } return 0; } 示例代码 2 读入和计算/判断写在一个循环里。 #include &lt;stdio.h&gt; int main() { int n; while(~scanf(&quot;%d&quot;, &amp;n)) { int ans = 0; double a[100], b[100]; //a为日平均气温，b为5天滑动均值 for(int i = 0; i &lt; n; ++i) { scanf(&quot;%lf&quot;, &amp;a[i]); if(i &gt;= 4) b[i] = (a[i] + a[i - 1] + a[i - 2] + a[i - 3] + a[i - 4]) / 5; if(!ans &amp;&amp; i &gt;= 8 &amp;&amp; b[i] &gt; 22 &amp;&amp; b[i - 1] &gt; 22 &amp;&amp; b[i - 2] &gt; 22 &amp;&amp; b[i - 3] &gt; 22 &amp;&amp; b[i - 4] &gt; 22) { for(int j = i - 8; j &lt; i - 3; ++j) if(a[j] &gt; 22) { ans = j + 1; break; } } } if(ans) printf(&quot;Success %d\\n&quot;, ans); else printf(&quot;Failure\\n&quot;); } return 0; } G Firefly\\text{Firefly}Firefly 小姐的彩灯 难度 考点 4 结构化编程，模拟，溢出判断 题目分析 - 思路 1 使用数组标记并更新每一轮亮着的彩灯以及每个彩灯剩余的秒数。 以下是模拟时需要注意的问题： 边界的彩灯在判断时可能出现越界问题 循环跳出的条件并不等于所有彩灯剩余秒数清零，当有一名彩灯的秒数上限为0时，其一侧的彩灯始终不会点亮。 使用数组更新彩灯标记时，不能直接在原数组上更新，需要另外开一个数组进行更新，更新完后整体拷贝到原数组上。 示例代码 1 直接模拟代码： #include &lt;stdio.h&gt; int a[105]; //剩余秒数 int b[105]; //上一秒亮着的彩灯 int c[105]; //这一秒亮着的彩灯 int main() { int n; scanf(&quot;%d&quot;, &amp;n); for (int i = 1; i &lt;= n; i++) { scanf(&quot;%d&quot;, &amp;a[i]); } int k; scanf(&quot;%d&quot;, &amp;k); b[k] = 1; while (1) { int sign = 0; // 标记有没有彩灯亮着 for (int i = 1; i &lt;= n; i++) // 遍历所有的彩灯 { if ((b[i - 1] || b[i] || b[i + 1]) &amp;&amp; a[i] &gt; 0) // 根据上一时刻两侧彩灯亮熄情况，判断这一个彩灯是否亮着 { // 在遍历判断第1只和第n只时，由于数组中0和n+1为合法下表且值始终为0，不会发生越界现象，也不影响或逻辑的判断 printf(&quot;%d &quot;, i); c[i] = 1; // 使用新数组标记亮着 sign = 1; // 标记本轮亮着 a[i]--; // 扣除剩余秒数 } else { c[i] = 0; } } for (int i = 1; i &lt;= n; i++) // 将c数组拷贝至b中 b[i] = c[i]; if (sign == 0) // 没有彩灯亮着，跳出 break; else printf(&quot;\\n&quot;); // 记得换行 } return 0; } 题目分析 - 思路 2 注意到，若一个彩灯被点亮过，那在该彩灯亮的时间到达上限之前，它会一直亮着，无需判断相邻彩灯是否亮。 我们维护一个区间 [l,r][l, r][l,r]，表示当前时刻编号在区间中的彩灯都被点亮过，最初 l=r=kl=r=kl=r=k。 从第二秒开始之后的每一秒，一般情况下编号为 l−1l-1l−1 和 r+1r+1r+1 的彩灯会开始发亮，此时 [l,r]=[l−1,r+1][l,r]=[l-1,r+1][l,r]=[l−1,r+1]。可见每一秒 lll 都会自减 111，rrr 都会自增 111​。 特殊情况是，如果编号为 l−1l-1l−1 或 r+1r+1r+1 的彩灯发光时间上限为 000，则此时 lll 或 rrr​ 不应该变化。 我们用数组 sss 来记录每个彩灯发光的剩余秒数。如果某个彩灯编号为 iii，在区间 [l,r][l,r][l,r] 中，且还有发光剩余秒数不为 000，则这一秒该彩灯应该发光，并且 s[i]s[i]s[i] 应该自减 111。 还需注意的是，如果 lll 已经到达左边界 111 了，lll 就不应该再变化了；rrr​ 同理。 用一个变量 flag 记录当前是否有彩灯亮着，当 flag == 0 的时候跳出循环。 示例代码 2 #include &lt;stdio.h&gt; int main() { int n, s[101], k; //s[i]表示编号为i的彩灯剩余秒数 scanf(&quot;%d&quot;, &amp;n); for(int i = 1; i &lt;= n; ++i) scanf(&quot;%d&quot;, &amp;s[i]); scanf(&quot;%d&quot;, &amp;k); int l = k, r = k; while(1) { if(l &gt; 1 &amp;&amp; s[l - 1]) l--; //l不为左边界，且s[l-1]不为0时，l自减1 if(r &lt; n &amp;&amp; s[r + 1]) r++; //r不为左边界，且s[r+1]不为0时，r自增1 int flag = 0; for(int j = l; j &lt;= r; ++j) if(s[j]) //s[j]不为0，剩余秒数不为0，亮着的秒数还未到达上限 { flag = 1; printf(&quot;%d &quot;, j); s[j]--; //更新s[j] } printf(&quot;\\n&quot;); //换行 if(!flag) break; } return 0; } Author: SiSi H Baymax的糖果王国 难度 考点 4~5 模拟，约瑟夫环 题目分析 本题考查用代码模拟题目描述的能力。 根据游戏规则，我们可以做出下述处理： 根据 n≤1000n \\leq 1000n≤1000，创建一个大小为 100510051005 的数组 a[1005]，用于储存糖果的编号并标记糖果是否被带离。 对于编号为 i (1≤1 \\leq1≤ i ≤n\\leq n≤n) 的糖果，可令 a[i] = i，当该糖果被带离时，为了避免移动数组元素，用 a[i] = 0 代表该糖果已经不在数组中。 对于每个轮次，可以用 while 循环模拟官员带离糖果的过程，当数到 a[i] = 0 的糖果时，跳过该糖果即可。每个轮次结束后，输出被带离糖果编号。 示例代码 1 #include &lt;stdio.h&gt; int a[1005]; //储存糖果的编号并标记糖果是否被带离 int main() { int n, k, m, left; scanf(&quot;%d%d%d&quot;, &amp;n, &amp;k, &amp;m); for (int i = 1; i &lt;= n; i++) //初始化每个糖果的编号 a[i] = i; left = n; //储存当前剩余糖果的数目 int front = n, back = 1; //初始时两个官员数糖果的位置 while (left &gt; 0) //当还有糖果未被带离时，游戏继续进行 { int kk = k, mm = m; while (kk--) //逆时针数k个 { do { front = front % n + 1; } while (a[front] == 0); //跳过已经被带离的糖果 } while (mm--) //顺时针数m个 { do { back = (back - 2 + n) % n + 1; } while (a[back] == 0); } printf(&quot;%d &quot;, front); //输出官员BBetula带离的糖果编号 left--; if (back != front) { printf(&quot;%d &quot;, back); //当两个官员选中不同糖果时，输出官员CBetula带离的编号 left--; } printf(&quot;\\n&quot;); a[front] = a[back] = 0; //将对应元素置0，标记对应糖果已被带离 } } 示例代码 2 与示例代码 1 的区别： 模拟数 kkk 或 mmm​ 个糖果时，使用for循环而非while 下标从 000 开始记，导致模运算细节不同。 #include&lt;stdio.h&gt; int a[1005]; int main() { int n, k, m; scanf(&quot;%d%d%d&quot;, &amp;n, &amp;k, &amp;m); int x = -1, y = n, cnt = 0; while(cnt &lt; n) { for(int i = 0; i &lt; k; ++i) //逆时针数k个 { x = (x + 1) % n; if(a[x]) i--; //如果该糖果已经被带离，则本次循环i应不变 } for(int i = 0; i &lt; m; ++i) //顺时针数m个 { y = (y + n - 1) % n; if(a[y]) i--; //如果该糖果已经被带离，则本次循环i应不变 } a[x] = 1, a[y] = 1; if(x == y) { printf(&quot;%d\\n&quot;, x + 1); cnt++; } else { printf(&quot;%d %d\\n&quot;, x + 1, y + 1); cnt += 2; } } return 0; } I 拆解平方数 难度 考点 6 数学计算，循环、分支结构（结构化编程） 题目分析 本题是 E1-H 拆解立方数的升级版，并在助教头子的指导下进行了二次升级。 相比于拆解立方数，本题的数据范围有了极大的增强，因此使用上次的两层循环枚举方法是完全不可取的。 对于在本题获得 TLE 0.3 的同学，应该想到了可以用类似队列或前缀和的思想。从数轴正半轴最左端开始，每当总和小于目标的时候，右侧数字入队使总和增大；每当总和大于目标时，左侧数字出队使总和减小。不过，该方法的效率依然不够理想，最坏情况的时间复杂度为 O(n)\\mathrm{O}(\\sqrt n)O(n​)，无法通过最后一个数据点。 那么是否存在更低时间复杂度的算法呢？由本题的 Hint 得知，对于符合条件的 nnn，有 n=(r−l)[2(r2+rl+l2)+3(r+l)+1]6n=\\frac{(r-l)[2(r^2+rl+l^2)+3(r+l)+1]}{6} n=6(r−l)[2(r2+rl+l2)+3(r+l)+1]​ 现在进行换元，令 s=r+l,d=r−ls=r+l, d=r-ls=r+l,d=r−l，化简得 6n=d(3s2+d22+3s+1)6n=d\\left(\\frac{3s^2+d^2}{2}+3s+1\\right) 6n=d(23s2+d2​+3s+1) 发现 ddd 是 6n6n6n 的一个因数，现在考虑对所有可能的 ddd 进行枚举。 注意到拆解平方数至少由两个连续正整数的平方和组成，可知 l≥0,r−l≥2l\\ge 0,r-l \\ge2l≥0,r−l≥2，即 sss 和 ddd 的取值范围为 s≥d≥2s \\ge d \\ge 2s≥d≥2，也能知道 ddd 枚举的下界是 222。 由 $ 6n &gt; d \\left (\\cfrac{3s2+d2}{2}\\right ) \\ge d\\left (\\cfrac{3d2+d2}{2} \\right )=2d^3$ 得出 d&lt;3n3d &lt; \\sqrt[3]{3n}d&lt;33n​，即 ddd 枚举的上界是 3n3\\sqrt[3]{3n}33n​，ddd 枚举的范围是 [2,3n3)[2,\\sqrt[3]{3n})[2,33n​)。又由于我们需要使拆解方法中包含的连续正整数尽量多，即 ddd 要尽量大，因此 ddd 从大往小枚举。第一个限制条件是 ddd 必须是 6n6n6n 的因数。 ddd 是我们枚举的，因此接下来，我们需要尝试用枚举出来的 ddd 求出 sss。令 6nd=y\\cfrac{6n}{d}=yd6n​=y，即 3s2+d22+3s+1=y\\cfrac{3s^2+d^2}{2}+3s+1=y23s2+d2​+3s+1=y，移项使得等式左边只包含 sss，得 $$s2+2s=\\cfrac{2y-d2-2}{3}$$ 等式右边的值都已知，我们可以得出第二个限制条件：2y−d2−22y-d^2-22y−d2−2 必须是 333 的整数倍。 令 2y−d2−23=c\\cfrac{2y-d^2-2}{3}=c32y−d2−2​=c，有 s2+2s=cs^2+2s=cs2+2s=c，由求根公式得到（舍去负根） s=−1+1+cs=-1+\\sqrt{1+c} s=−1+1+c​ 当然，此时算出来的 sss 必须是整数，这是第三个限制条件。此外，最终求出的 sss 应不小于 ddd，这是第四个限制条件。 满足以上四个限制条件，我们就找到了一组解。 现在我们可以由 s,ds,ds,d 轻松算出，题目中 a=l+1=s−d2+1,b=r=s+d2a=l+1=\\cfrac{s-d}{2}+1, b=r=\\cfrac{s+d}{2}a=l+1=2s−d​+1,b=r=2s+d​。 由上述算法需要的枚举范围可以得出，每组数据最坏情况的时间复杂度为 O(n3)\\mathrm{O}(\\sqrt[3]{n})O(3n​)。 示例代码 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main() { int T; scanf(&quot;%d&quot;, &amp;T); while (T--) { long long n; scanf(&quot;%lld&quot;, &amp;n); int flag = 0; for (long long d = pow(3 * n, 1.0 / 3.0); d &gt; 1; d--) { // 限制条件一 if (6 * n % d != 0) // 6n/d必须是一个整数 continue; long long c, y; y = 6 * n / d; c = 2 * y - d * d - 2; // 限制条件二 if (c % 3 != 0) // (2y-d*d-2)/3 必须是一个整数 continue; c /= 3; long long s = sqrt(1 + c) - 1; // 将double赋给整型变量，会自动向下取整 // 此处写作 s = round(sqrt(1 + c) - 1); 更为保险，避免由于double精度问题导致计算错误 // 限制条件三 if (s * s + 2 * s != c) // 若代回原方程不成立，说明s不等于sqrt(1+c)-1，即sqrt(1+c)-1不为整数 continue; // 限制条件四 if (s &lt; d) // s 必须大于等于 d continue; // 此时就找到了一组解 flag = 1; long long a = (s - d) / 2 + 1, b = (s + d) / 2; printf(&quot;%lld %lld\\n&quot;, a, b); break; } if (!flag) // 始终没有找到解 printf(&quot;NO\\n&quot;); } return 0; } 拓展思考 最后计算出来的 s,ds,ds,d 一定同奇偶吗？为什么呢？（提示：ddd 与 ccc 同奇偶。） 循环里面一定需要判断 s≥ds\\ge ds≥d 是否成立吗？ J czx 的气球爆破 难度 知识点 6~7 贪心 题目分析 思路一 考虑相同颜色的两个相邻气球，前一个在位置 iii，后一个在位置 nxti{nxt}_inxti​，颜色为 ccc。显然，当位置 nxti{nxt}_inxti​ 的气球未被消除时，不可能选择颜色 ccc 来消除在位置 iii 的气球。如果我们想要选择颜色 ccc 来消除位置 iii 和 iii 之后的的气球，当且仅当位置 nxti{nxt}_inxti​ 的气球已被消除。也就是说，此时末尾气球的位置必须要在 [i,nxti−1][i,{nxt}_i-1][i,nxti​−1] 中。 现在我们对每一个气球都考虑这件事。nxtinxt_inxti​ 表示与第 iii 个气球颜色相同的下一个气球的位置，那么消除第 iii 个气球与 iii 之后的所有气球，需要末尾气球在位置在 [i,nxti−1][i,nxt_i-1][i,nxti​−1] 中。 可以认为，消除位置 iii 上的气球，等同于消除 [i,nxti−1][i, {nxt}_i - 1][i,nxti​−1] 区间内的所有气球。 那么，对于每个气球，它实际上都有一个以其位置为左端点的区间，进而可以看作一个长度为 nnn 的大区间内有 nnn 个小线段，要在这 nnn 个小线段选出最少的线段来覆盖整个大区间，这是典型的区间覆盖问题。 区间覆盖问题一般用贪心的思想来解决。先将线段按 左端点从小到大排序（本题里线段本身即有序，可以不需要排序），每次在可以覆盖到目标区间的所有线段中选择右端点最大的线段，然后将目标线段的左端点设为其右端点+1。左端点排序保证可以覆盖到目标区间的线段是连续的，因此做到复杂度 O(n)O(n)O(n)​。 思路二 另外，这题还能有另一种贪心的考虑方案。 假设当前总共有 numnumnum 种颜色的气球存在，那么，我们从右往左扫，如果加上当前扫到的气球颜色后，已经扫描到的气球颜色恰好等于 numnumnum，那么我们就选择现在这个气球的颜色，贪心的正确性显然。重复这个操作，直到扫描完所有气球。这个做法的难点在于如何快速高效的维护当前扫到的所有的气球颜色（因为每次扫描完需要进行数组清空，而这个操作可能会花费大量的时间导致 TLE）。可以用一个新的数组记录需要清空的气球颜色，在清空时只清空这个数组中出现过的元素即可。 示例代码 1 思路一 #include &lt;stdio.h&gt; #define N 200005 #define max(a, b) ((a) &gt; (b) ? (a) : (b)) int col[N], n, nxt[N], lst[N]; int main() { int T; scanf(&quot;%d&quot;, &amp;T); while (T--) { scanf(&quot;%d&quot;, &amp;n); for (int i = 1; i &lt;= n; i++) { scanf(&quot;%d&quot;, &amp;col[i]); nxt[i] = lst[col[i]] = n + 1; } for (int i = n; i &gt;= 1; i--) { nxt[i] = lst[col[i]] - 1; lst[col[i]] = i; } int pos = 1, res = 0, maxx = 0; for (int i = 1; i &lt;= n; i++) { maxx = max(maxx, nxt[i]); if (i &gt;= pos) { res++; pos = maxx + 1; maxx = 0; } } printf(&quot;%d\\n&quot;, res); } return 0; } 示例代码 2 思路二 #include &lt;stdio.h&gt; #include &lt;string.h&gt; #define MIN(a, b) (a &lt; b ? a : b) int arr[200005]; int cnt[200005]; int pos[200005]; int flag[200005] = {}; int main(void) { int T, n, i, tailpos, total, ans, t, sub, j; memset(pos, -1, sizeof(pos)); scanf(&quot;%d&quot;, &amp;T); while (T--) { scanf(&quot;%d&quot;, &amp;n); total = 0, ans = 0; for (i = 0; i &lt; n; i++) { scanf(&quot;%d&quot;, &amp;arr[i]); if (cnt[arr[i]] == 0) total++; cnt[arr[i]]++; } j = 0; for (i = 0; i &lt; n; i++) { if(pos[arr[i]] == -1) { pos[arr[i]] = j; j++; } } i = n - 1; while (i &gt;= 0) { for (j = 0; j &lt; total; j++) { flag[j] = 0; } sub = 0, t = 0; while (t &lt; total) { cnt[arr[i]]--; if (!flag[pos[arr[i]]]) { flag[pos[arr[i]]] = 1; t++; } if (cnt[arr[i]] == 0) { sub++; } i--; } total -= sub; ans++; } printf(&quot;%d\\n&quot;, ans); for (j = 0; j &lt; n; j++) { pos[arr[j]] = -1; } } return 0; } 示例代码 3 与思路一贪心策略基本相同，采用维护区间 [l,r)[l,r)[l,r) 的方式，遍历过程 l,rl,rl,r 只增不减。 其中，数组 bbb 记录的是区间内每个颜色出现次数。 #include&lt;stdio.h&gt; int a[200005], b[200005]; int main() { int T; scanf(&quot;%d&quot;, &amp;T); while(T--) { int n; scanf(&quot;%d&quot;, &amp;n); for(int i = 0; i &lt; n; ++i) scanf(&quot;%d&quot;, &amp;a[i]), b[i + 1] = 0; int l = 0, r = 0, last_r = 0, ans = 0; while(r &lt; n) { do b[a[r++]]++; while(r &lt; n &amp;&amp; a[r] != a[l]); if(r == n) { ans++; break; } do b[a[l++]]--; while(b[a[l]] &gt; 1); if(l &gt; last_r) { last_r = r; ans++; } } printf(&quot;%d\\n&quot;, ans); } return 0; } - End - ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"C3 - Solution-23航c","link":"https://github.pansis.site/post/c3-solution-23-hang-c/","stats":{"text":"30 min read","time":1749000,"words":6444,"minutes":30},"dateFormat":"2024-03-22"},{"content":"A 号令百鸟 难度 考点 1 存储范围 题意分析 对是否能形成三角形这一问题，常用的判据是“任意两边之和大于第三边”。在这一题中，考虑到即使两个数都在 int 范围内，它们的和仍可能超出int范围，可以使用 long long 类型进行存储。 另外，再次建议对类似题型复制粘贴输出。 示例代码 1 #include&lt;stdio.h&gt; long long a, b, c; int main() { while (scanf(&quot;%lld%lld%lld&quot;, &amp;a, &amp;b, &amp;c) != EOF) { //只有三个不等式都成立才能形成三角形 if ((a + b &gt; c) &amp;&amp; (a + c &gt; b) &amp;&amp; (b + c &gt; a)) printf(&quot;We11 done\\n&quot;); else printf(&quot;N0pe\\n&quot;); } return 0; } 示例代码 2 int 内的正整数减法不会溢出，可以利用这个性质而不需要 long long。 需要注意的是 int 内两个整数相减可能会溢出。 #include&lt;stdio.h&gt; int main() { int a, b, c; while(~scanf(&quot;%d%d%d&quot;, &amp;a, &amp;b, &amp;c)) { if (a - b &lt; c &amp;&amp; b - c &lt; a &amp;&amp; c - a &lt; b) printf(&quot;We11 done\\n&quot;); else printf(&quot;N0pe\\n&quot;); } return 0; } B 小宇的圆周率 难度 考点 2 循环 题目分析 这道题主要考察循环和浮点数的计算。 公式 ∑n=0∞(−1)n2n+1\\sum_{n=0}^{\\infty}\\frac{(-1)^n}{2n+1}∑n=0∞​2n+1(−1)n​ 中数列符号与 nnn 的奇偶性有关。我们可以采取使用条件语句进行奇偶性判断。我们也可以采用示例代码中的方法，定义变量sign ,初始值为 111 ，每一次循环结束时，signsignsign 变量自乘 −1-1−1 。 这样即可作为第 nnn 项的符号。 在整数除以整数时，如果想要得到浮点数，一定要在表达式中乘以 1.01.01.0，或者使用强制类型转换 ，使表达式的计算范围扩展到浮点数范围。 这道题 nnn 的范围是 $1\\le n \\le 10^{5} $ 。在公式 222 中需要计算 (2n+1)2(2n+1)^2(2n+1)2 ，当 nnn 接近给定范围的上限时，(2n+1)2(2n+1)^2(2n+1)2 的大小会超过 int 范围。所以需要使用表达式((2ll * i + 1) * (2 * i + 1)) 进行计算，其中的 2ll 可以使表达式在 long long 范围内计算。 示例代码 1 #include&lt;stdio.h&gt; #include &lt;math.h&gt; int main() { int t; int n; scanf(&quot;%d&quot;, &amp;t); while (t--) {//循环t次 scanf(&quot;%d&quot;, &amp;n); double col1 = 0; int sign = 1; for (int i = 0; i &lt; n; ++i) { col1 += (1.0 * sign / (2 * i + 1)); sign *= -1; } col1 *= 4; //计算公式1的pi double col2 = 0; for (int i = 0; i &lt; n; ++i) { col2 += (1.0 / ((2ll * i + 1) * (2 * i + 1))); } col2 = sqrt(8 * col2); //计算公式2的pi printf(&quot;%.6f\\n&quot;, fabs(col1 - col2));//输出，保留6位小数。注意浮点数绝对值使用fabs函数。 } return 0; } 示例代码 2 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main() { int t; scanf(&quot;%d&quot;, &amp;t); while(t--) { int n; scanf(&quot;%d&quot;, &amp;n); double pi_1 = 0, pi_2 = 0; for(int i = 0; i &lt; n; i++) { pi_1 += pow(-1, i) / (2 * i + 1); pi_2 += 1 / pow(2 * i + 1, 2); } pi_1 *= 4; pi_2 = sqrt(pi_2 * 8); printf(&quot;%.6f\\n&quot;, fabs(pi_1 - pi_2)); } return 0; } Author：pyhAuthor：pyhAuthor：pyh C 数字大冒险 难度 考点 2~3 原码，反码，补码 题目分析 本题题意很明确，即给定一个 int 范围内的整数 nnn，输出其原码、反码、补码。 下面分别介绍一下原码、反码、补码的定义： 原码：将最高位作为符号位（000 表示正，111 表示负），其它数字位代表数值本身的绝对值的数字表示方式。 反码：如果是正数，则表示方法和原码一样；如果是负数，符号位不变，其余各位取反，则得到这个数字的反码表示形式。 补码：如果是正数，则表示方法和原码一样；如果是负数，则将数字的反码加上 111（相当于将原码数值位取反然后在最低位加 111​） 所以，根据计算机以补码储存数据的特点，我们可以很容易地求出任意一个数的原、反、补码。 示例代码 1 #include &lt;stdio.h&gt; int main() { int num; while (~scanf(&quot;%d&quot;, &amp;num)) //当scanf返回-1时，~(-1)为0，跳出循环 { if (num &gt;= 0) //num为0或正整数，其原反补码均相同 { for (int i = 0; i &lt; 3;i++) //连续输出三次补码 { for (int j = 31; j &gt;= 0; j--) //依次输出每一位 { printf(&quot;%d&quot;, (num &gt;&gt; j) &amp; 1); //&amp;1相当于取出当前最低位的二进制编码 } printf(&quot;\\n&quot;); } } else { // 打印原码 putchar('1'); //符号位i for (int i = 30; i &gt;= 0; i--) { printf(&quot;%d&quot;, (-num &gt;&gt; i) &amp; 1); } printf(&quot;\\n&quot;); //打印反码 putchar('1'); for (int i = 30; i &gt;= 0; i--) { printf(&quot;%d&quot;, !((-num &gt;&gt; i) &amp; 1)); //对每一个数值位取反 } printf(&quot;\\n&quot;); //打印补码 for (int i = 31; i &gt;= 0; i--) { printf(&quot;%d&quot;, (num &gt;&gt; i) &amp; 1); //根据计算机以补码储存数据的特点，直接打印每一位即可得补码 } printf(&quot;\\n&quot;); } } } 示例代码 2 当我们学了函数之后，可以把代码中重复的部分写成函数，这样可以使代码变得非常精简。 #include &lt;stdio.h&gt; void print(int n) //此函数作用为打印n的补码 { for (int i = 31; i &gt;= 0; --i) printf(&quot;%d&quot;, n &gt;&gt; i &amp; 1); puts(&quot;&quot;); } int main() { int n; while (~scanf(&quot;%d&quot;, &amp;n)) { if (n &gt;= 0) //原码、反码、补码均相同 { print(n); print(n); print(n); } else { //原码 print((1 &lt;&lt; 31) - n); //先把符号位设置为1，再加上数值位 //反码 print(n - 1); //n以补码存储，-1可得反码 //补码 print(n); } } return 0; } D 寻找码片 难度 考点 2 二进制枚举，位运算，阅读材料 大致题意 给定数字的二进制位数 mmm 和数字 nnn，求所有与该数字不同二进制位数量为 m2\\frac{m}{2}2m​ 的数，并按从小到大的顺序输出。 题目分析 这个题目的题干有点复杂，我们只需要抓住要点即可。 首先，题干中说两个 mmm 位的码片 SSS 和 TTT 相互正交当且仅当满足下面的公式： 1m∑i=1mSiTi=0\\frac{1}{m}\\sum^{m}_{i = 1}S_iT_i = 0 m1​i=1∑m​Si​Ti​=0 由这个公式可以得到什么呢？可以发现，两个数 x,yx,yx,y 的某一个二进制位如果不同，则其码片这一位的规格化内积必为 −1-1−1，反之必为 111。由这个性质，结合码片所有位的规格化内积之和为 000，可以推出这两个数不同的二进制位和相同的二进制位的数量相等，均为 m2\\frac{m}{2}2m​。 同时，题干要求按从小到大的顺序输出符合要求的数。由于 mmm 位无符号二进制数可以表示的范围是 000 到 2m−12^m - 12m−1，为此我们可以使用二进制枚举的方法，从 000 到 2m−12^{m} - 12m−1 进行枚举，如果当前枚举的数符合要求就输出，否则不输出。 示例代码 #include&lt;stdio.h&gt; int main() { int m; int n; // 基准码片 scanf(&quot;%d%d&quot;, &amp;m, &amp;n); for (int i = 0; i &lt; (1 &lt;&lt; m); ++i) { // 从 0 开始枚举候选码片，枚举到 2^m - 1 int cnt = 0; // 不同同二进制位计数 int tmp = i ^ n; // 用 i ^ n，利用异或的性质求出不同的二进制位，如果 i 和 tar 某一位不同，则 tmp 此位为 1，否则为 0. for (int j = 0; j &lt; m; ++j) { cnt += (tmp &gt;&gt; j) &amp; 1; // 统计有多少个不同的二进制位，+= 是缩减运算符，等价于 cnt = cnt + (tmp &gt;&gt; j) &amp; 1; } if (cnt == m / 2) printf(&quot;%d\\n&quot;, i); // 如果不同的二进制为数码等于 m / 2，则符合要求，输出。 } return 0; } E 数字滑坡数 难度 知识点 3 循环 题目分析 这道题目要求判断一个正整数是否为数字滑坡数。数字滑坡数的定义是每一位数字都比它低一位的数字大，也就是从高位到低位递减。我们可以通过遍历该数字的每一位来进行判断。 具体的判断流程如下： 从输入中读取一个正整数 NNN。 取个位数字作为初始比较值，将 NNN 除以 101010。 循环遍历 NNN 的每一位数字，若当前位数字小于等于上一位数字，则将标志位 ok 置为 000，否则更新比较值为当前位数字，继续遍历下一位。 最后根据标志位 ok 的值输出结果。 示例代码 #include &lt;stdio.h&gt; int main() { int n; while (~scanf(&quot;%d&quot;, &amp;n)) { int num = n % 10, ok = 1;// 初始化 n /= 10; while (n) { if (n % 10 &lt;= num) { ok = 0; break; // 失败，做标记后直接退出 } else { num = n % 10; n /= 10; // 更新比较数，继续循环 } } if(ok) printf(&quot;Yes\\n&quot;); else printf(&quot;No\\n&quot;); } return 0; } F Cirno 的完美位运算教室 难度 考点 4 位运算 问题分析 题意很清晰，我们应当使 xxx 和 yyy 的二进制位上至少有一位相同（按位与），有一位不同（按位异或），那么我们可以对 xxx 的值进行分类讨论： 若 x=1x=1x=1 ，显然 y=3y=3y=3； 若 xxx 的二进制位上只有一位为 111 ，由于 x≠1x \\neq 1x​=1，仅需令 y=x+1y=x+1y=x+1 即可满足题意； 若 xxx 的二进制位上有多个 111，只需取 xxx 的最低位的 111 赋给 yyy 即可。 参考代码 1 #include &lt;stdio.h&gt; int main() { int T, x, i, y; scanf(&quot;%d&quot;, &amp;T); while (T--) { scanf(&quot;%d&quot;, &amp;x); if (x == 1) y = 3; else { for (i = 0; i &lt; 32; i++) { if ((x &gt;&gt; i) &amp; 1) // x的二进制位为1的最低位 { y = (1 &lt;&lt; i); break; } } if (y == x) // x仅有一位二进制位为1 y++; } printf(&quot;%d\\n&quot;, y); } return 0; } 小拓展 关于 lowbit⁡\\operatorname{lowbit}lowbit 运算 我们定义一个函数 f(x)=lowbit⁡(x)f(x)=\\operatorname{lowbit}(x)f(x)=lowbit(x)，函数值为 xxx 的二进制最低位的 111 所对应的值。 例如：6=(110)26=(110)_26=(110)2​，那么 lowbit⁡(6)=(10)2=2\\operatorname{lowbit}(6)=(10)_2=2lowbit(6)=(10)2​=2，因为 (110)2(110)_2(110)2​ 的最低位的 111 对应的数为 21=22^1=221=2。 lowbit⁡\\operatorname{lowbit}lowbit 运算的实现 为得到 lowbit⁡\\operatorname{lowbit}lowbit 的值，我们只需得到最低位的 111 的位置，将其余位置全部置 000 即可，下面介绍两种方式： x&amp;(x^(x-1)) 对 xxx 的取值进行讨论： 若 xxx 为奇数，显然运算结果为 111，符合要求； 若 xxx 为偶数，那么 x−1x-1x−1 会将 xxx 从最低位的 111 开始一直到最右位全部取反，即得到一个前面不变，后面为 011…011 \\dots011… 的串，与 xxx 进行按位异或，得到一个前面为 000，后面为 111…111 \\dots111… 的串，再与 xxx 进行按位与，即得到 lowbit⁡\\operatorname{lowbit}lowbit。 x&amp;-x 我们知道，一个负数的补码是其绝对值的原码取反加一，有了这个前置知识，这个运算的实现原理留给读者思考。 lowbit⁡\\operatorname{lowbit}lowbit 运算有很多用途，比如可以用来统计一个数的二进制位为 111 的个数： while (x) { x -= x &amp; -x; cnt++; } 有了 lowbit⁡\\operatorname{lowbit}lowbit 运算，我们的代码可以得到一些简化。 参考代码 2 #include &lt;stdio.h&gt; int main() { int T, x, y; scanf(&quot;%d&quot;, &amp;T); while (T--) { scanf(&quot;%d&quot;, &amp;x); if (x == 1) y = 3; else if (x - (x &amp; -x)) // x的二进制有多位为1 y = x &amp; -x; else y = x + 1; printf(&quot;%d\\n&quot;, y); } return 0; } G Baymax的密码宝藏 难度 考点 4 二进制、双进位判断溢出 题目分析 计算机采用补码运算时，若结果的数值超过了补码所能表示的数据范围，则计算结果错误，这种情况称为溢出。比如，对于 char 类型变量而言，其所能表示的范围为 −128∼127-128 \\sim 127−128∼127，所以当计算 127+1127 + 1127+1时，便会得到错误的结果。 直观上来说，溢出有两种情况： 当两个较大正数相加时，产生 正数 + 正数 = 负数 的现象，这种情况称为「上溢 」。 当两个较小的负数相加时，产生 负数 + 负数 = 正数 的现象，这种情况称为 「下溢 」。 那么应该如何判断溢出呢？ 首先我们先看「上溢 」。两个正数相加，它们的符号位都是 000，如果它们数值位的最高位都是 111，则会导致相加时向符号位进位，从而导致和的符号位为 111，和为负数；如果它们数值位最高位一个为 111，另一个为 000，则我们应该继续看数值位的下一位，直到数值位相同时停止，并据此判断数值位最高位是否进位。 对于「下溢 」，两个负数相加，它们的符号位都是 111，相加时符号位会进位变为 000，如果数值位最高位相加时不向符号位进位，则和将变为正数，导致错误结果。 所以，当且仅当符号位与数值位最高位同时进位或者同时不进位时，相加才不会溢出。 示例代码 1 //从后往前判断 #include &lt;stdio.h&gt; int num1[100005]; int num2[100005]; int main() { int t,n,c1,c2,flag;//flag标记下次运算时是否进位 scanf(&quot;%d%d&quot;, &amp;t, &amp;n); while (t--) { for (int i = 0;i&lt;n;i++) scanf(&quot;%1d&quot;, &amp;num1[i]); for (int i = 0; i &lt; n; i++) scanf(&quot;%1d&quot;, &amp;num2[i]); c1=c2=flag = 0; for (int i = n - 1; i &gt;= 0;i--) //从最低位开始进行相加 { if((num1[i]==1 &amp;&amp; num2[i]==1) || ((num1[i] + num2[i]) == 1 &amp;&amp; flag)) //本次运算时进位 { flag = 1; if(i==0) //符号位进位 c1 = 1; if(i==1) //数值位最高位进位 c2 = 1; } else flag = 0; } if(c1^c2) //不同时进位 printf(&quot;0verFLOW!\\n&quot;); else //同时进位 printf(&quot;Not 0verFLOW\\n&quot;); } } 示例代码 2 //从前往后进行判断 #include &lt;stdio.h&gt; int num1[10005], num2[10005]; int main() { int t, n, flag,i; scanf(&quot;%d%d&quot;, &amp;t, &amp;n); while (t--) { flag = 0; for (int i = 0; i &lt; n; i++) scanf(&quot;%1d&quot;, &amp;num1[i]); for (int i = 0; i &lt; n; i++) scanf(&quot;%1d&quot;, &amp;num2[i]); if (num1[0] == num2[0]) //符号位相同 { for (i = 1; i &lt; n; i++) { if (num1[i] == num2[i]) //找到第一个数值相同位 { if (num1[i] != num1[0]) //该数值位和符号位不相同则溢出 flag = 1; break; } } if(i==n) //如果除了符号位相同外，每一个数值位都不同，则溢出取决于符号位 flag = num1[0]; } if (flag) printf(&quot;0verFLOW!\\n&quot;); else printf(&quot;Not 0verFLOW\\n&quot;); } return 0; } 示例代码 3 用数组模拟高精度二进制加法。 #include &lt;stdio.h&gt; int a[10005], b[10005]; int main() { int t, n; scanf(&quot;%d%d&quot;, &amp;t, &amp;n); while(t--) { for(int i = 1; i &lt;= n; ++i) scanf(&quot;%1d&quot;, &amp;a[i]); for(int i = 1; i &lt;= n; ++i) scanf(&quot;%1d&quot;, &amp;b[i]); if(a[1] != b[1]) //若符号不同，肯定不会溢出 printf(&quot;Not 0verFLOW\\n&quot;); else { for(int i = n; i &gt; 0; --i) //将b加到a上 { a[i] += b[i]; if(i &gt; 0 &amp;&amp; a[i] &gt;= 2) //进位 a[i] -= 2, a[i - 1]++; } if(a[1] == b[1]) //如果a+b的符号位没有改变，则没有溢出 printf(&quot;Not 0verFLOW\\n&quot;); else //如果a+b的符号位改变了，则溢出 printf(&quot;0verFLOW!\\n&quot;); } } return 0; } H 铁道速度学 难度 考点 4 浮点运算，模拟 题目分析 使用数组实时更新每名角色剩余所需的抵达终点的时间，每次模拟时，在该数组中寻找抵达终点所需时间最少的角色，其余角色减去该角色抵达终点所需时间，并令该角色返回起点并重置所需时间。 示例代码 #include &lt;stdio.h&gt; #include &lt;math.h&gt; #define eps 1e-9 int main() { int n; double T; scanf(&quot;%d %lf&quot;, &amp;n, &amp;T); char s[6][30]; double speed[6]; double t[6]; // 储存每次模拟后每名角色剩余抵达终点的所需时间 for (int i = 0; i &lt; n; i++){ scanf(&quot;%s %lf&quot;, s[i], &amp;speed[i]); t[i] = 10000 / speed[i]; // 初始化t数组的值 } // 模拟过程（注意浮点数判等！！！！！） while (fabs(T) &gt;= eps) { double minus = t[0]; for (int i = 0; i &lt; n; i++) { // 寻找下次抵达终点最早的角色 if (minus &gt; t[i]) minus = t[i]; } T -= minus; // 更新剩余倒计时 if (T &lt; -eps) { // 倒计时消耗完毕 break; } for (int i = 0; i &lt; n; i++) { t[i] -= minus; // 更新角色剩余抵达终点的所需时间 if (fabs(t[i]) &lt; eps) { // 更新后剩余时间为0，为当次行动角色，输出名字并重置时间 printf(&quot;%s &quot;, s[i]); t[i] = 10000 / speed[i]; } } } } Author: SiSi I 赌怪2024 难度 知识点 5~6 异或前缀和 题目分析 每次翻转即是将 [l,r][l,r][l,r] 区间中所有原本正面的变成反面，所有原本反面的变成正面。即“原本在的现在不在了，原本不在的现在在了”。利用异或的性质 a⊕a=0a\\oplus a=0a⊕a=0，每次翻转只需要将原来的正反面异或和都与 [l,r][l,r][l,r] 区间的异或和异或一下，便可以达到效果。求区间异或和用异或前缀和来实现。 异或前缀和是指数组中前缀异或和的一种形式。在计算异或前缀和时，我们通过对数组元素从第一个元素开始依次进行异或运算，得到每个位置处的前缀异或和。 下面是异或前缀和的原理： 给定一个数组 aaa，我们可以构建一个新的数组 xsumxsumxsum，其中 xsumixsum_ixsumi​ 表示数组 aaa 的前 iii 个元素（包括第 iii 个元素）的异或值。则有以下关系成立： xsum0=0xsum_0 = 0 xsum0​=0 xsumi=a0⊕a1⊕...⊕ai−1xsum_i = a_0 \\oplus a_1 \\oplus ... \\oplus a_{i-1} xsumi​=a0​⊕a1​⊕...⊕ai−1​ （xsumixsum_ixsumi​表示数组 a 的前 i 个元素的异或值） 根据以上关系，我们可以很容易计算出数组 xsumxsumxsum 中的所有值，这样就得到了数组 aaa 的异或前缀和数组 xsumxsumxsum。 在实际应用中，异或前缀和的一个重要性质是：若要求得数组 aaa 中下标区间 [l,r][l, r][l,r] 的元素的异或和，只需计算 xsum[l-1] 和 xsum[r] 的异或值即可，即 al⊕al+1⊕...⊕ar=xsuml−1⊕xsumra_l \\oplus a_{l+1} \\oplus ... \\oplus a_r = xsum_{l-1} \\oplus xsum_ral​⊕al+1​⊕...⊕ar​=xsuml−1​⊕xsumr​。 通过异或前缀和，我们可以在 O(1)O(1)O(1) 的时间复杂度内快速计算任意区间的异或和，从而解决一些涉及区间异或操作的问题，提高算法效率。 示例代码 #include &lt;stdio.h&gt; #include &lt;string.h&gt; #define maxN 200000 int n, xsum[maxN + 5], a[maxN + 5], q, res[2], op; //0表示反面，1表示正面 char s[maxN + 5]; int main() { scanf(&quot;%d&quot;, &amp;n); scanf(&quot;%s&quot;, s + 1); for (int i = 1; i &lt;= n; i++) { scanf(&quot;%d&quot;, a + i); xsum[i] = xsum[i - 1] ^ a[i]; res[s[i] - '0'] ^= a[i]; } scanf(&quot;%d&quot;, &amp;q); while (q--) { scanf(&quot;%d&quot;, &amp;op); if (op == 0) { int l, r; scanf(&quot;%d%d&quot;, &amp;l, &amp;r); res[0] ^= xsum[r] ^ xsum[l - 1]; res[1] ^= xsum[r] ^ xsum[l - 1]; } else if (op == 1) { int x; scanf(&quot;%d&quot;, &amp;x); printf(&quot;%d &quot;, res[x]); } } return 0; } J 仙人的异或数组pro 难度 考点 7 位运算+贪心 题目分析 前置知识 首先我们要先了解关于按位异或的两个基本性质： 0 ⨁\\bigoplus⨁ 1 = 1 , 0 ⨁\\bigoplus⨁ 0 = 0 b = a ⨁\\bigoplus⨁ b ⨁\\bigoplus⨁ a 以及按位或的一个性质：多个数按位或，任意一个数的第 i 位为1，则结果的第 i 位为1 思路分析 题目基本意思比较明确：要求将数列分成 M 段，求每段的异或和，让最后每段异或和的按位或最小。对于两个数比较大小，我们总是优先比较高位数字大小，所以我们很容易想到贪心算法：从高位向低位贪心，尽量保证高位为0，要使得最后按位或结果为0，则这M段每段的按位异或都为0。 代码设计 对于第 j位，从前往后计算前缀异或和，统计前缀异或和出现 0 的次数，若小于 M，则说明该位无论怎么分,最后按位或结果都是 1。这里要注意的是，0 的次数可能会大于 M，因为我们用的是前缀和，两个相邻 0 合并之后异或和还是 0，这说明在这里可能存在多种分段方法。 因为贪心思想为尽量保证高位为 0，所以当低位的分发与高位相冲突，并且不能存在其他方案时，最后的按位或结果也是 0。 那么我们需要将高位不能作为断点的位置标记下来，用 tab=1 标记，在枚举低位的时候，要同时进行判断异或和是否为 0 以及该点是否为高位断点。 示例代码 #include&lt;stdio.h&gt; int n, m; unsigned int a[500005], tab[500005], ans; int main() { scanf(&quot;%d %d&quot;, &amp;n, &amp;m); for(int i = 1; i &lt;= n; i++) scanf(&quot;%u&quot;, &amp;a[i]); for(int j = 31; j &gt;= 0; j--) //unsigned int 位数不超过32位 { unsigned int temp = 0, sum = 0; //temp为前缀和，sum为统计temp出现0的次数 for(int i = 1; i &lt;= n; i++) { temp = temp ^ ((a[i] &gt;&gt; j) &amp; 1); if(!temp &amp;&amp; !tab[i]) sum++; //前缀和为0，并且该位置在高位时并未被标记 } if(temp || sum &lt; m) //temp最后如果为1或者无法分成m段，则该位无论怎么分都只能为1 { ans = ans + (1u &lt;&lt; j); continue; } //能进行到这一步说明一定能有一种方案使得该位最后按位或结果为0 temp = 0; for(int i = 1; i &lt;= n; i++) //进行标记，如果temp为1，则该点一定不能被断 { temp = temp ^ ((a[i] &gt;&gt; j) &amp; 1); if(temp &amp;&amp; !tab[i]) tab[i] = 1; } } printf(&quot;%u&quot;, ans); return 0; } - End - ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"E2 - Solution-23航c","link":"https://github.pansis.site/post/e2-solution-23-hang-c/","stats":{"text":"25 min read","time":1466000,"words":5612,"minutes":25},"dateFormat":"2024-03-22"},{"content":"A 奇异的位运算2024 难度 考点 1 位运算 题目分析 本题考察位运算。 根据hint，可以得知我们只需要把两个数异或起来，再按位取反，即可得到结果，注意运算的优先级，搞不清楚优先级多加括号即可。 示例代码 1 #include &lt;stdio.h&gt; int main() { unsigned a, b; while(scanf(&quot;%u%u&quot;, &amp;a, &amp;b) != EOF) { printf(&quot;%u\\n&quot;, ~(a ^ b)); //printf(&quot;%u\\n&quot;, ~a ^ b); //先对a取反，再和b异或，结果一样 } return 0; } 扩展阅读 如果我没能观察出这个运算性质，也没有hint，该怎么办呢？其实，也可一位一位地计算，通过四个并列的if-else语句即可。 示例代码 2 #include &lt;stdio.h&gt; int main() { unsigned a, b; while (scanf(&quot;%u%u&quot;, &amp;a, &amp;b) != EOF) { unsigned int ans = 0; for (int i = 0; i &lt;= 31; i++) { unsigned int m = (a &gt;&gt; i) &amp; 1, n = (b &gt;&gt; i) &amp; 1; //每次取出a，b的每一位 if (m == 0 &amp;&amp; n == 0) { ans = ans | (1 &lt;&lt; i); } else if (m == 1 &amp;&amp; n == 1) { ans = ans | (1 &lt;&lt; i); } else if (m == 0 &amp;&amp; n == 1) { ans = ans | (0 &lt;&lt; i); } else if (m == 1 &amp;&amp; n == 0) { ans = ans | (0 &lt;&lt; i); } } printf(&quot;%u\\n&quot;, ans); } return 0; } B 小宇的基物实验物资 难度 考点 1 位运算 题目分析 二进制是一种用 000 和 111 表示数字的方法，每个数字都可以用一串 000 和 111 来表示，例如：141414 的二进制表示为 1110 二进制中每个位上的数字都代表一个幂次，从右往左数，从第 000 位开始计，第 000 位代表 202^020，第 111 位代表 212^121，第 222 位代表 222^222，以此类推 二进制中每个位上的数字乘以对应的幂次就是该位上的值，例如：1110 中第 000 位为 000，则值为 $ 0×2^0=0 $；第 111 位为 111，则值为 1×21=21×2^1=21×21=2；第 222 位为 111，则值为1×22=41×2^2=41×22=4；第 333 位为111，则值为1×23=81×2^3=81×23=8 二进制中所有位上的值相加就是该数字的十进制表示，例如：(1110)2=8+4+2+0=14(1110)_2=8+4+2+0=14(1110)2​=8+4+2+0=14 因此，如果要用二的幂次来表示一个数字kkk，只需要找出它的二进制表示中哪些位为111，然后输出对应的幂次即可 示例代码 #include &lt;stdio.h&gt; int main() { unsigned int b;//注意b的数据类型应为unsigned int scanf(&quot;%u&quot;, &amp;b); for (int i = 0; i &lt; 32; ++i) { if ((1u &amp; (b &gt;&gt; i)) != 0) //判定第i位是否为0 { printf(&quot;%u &quot;, 1u &lt;&lt; i);//如果第i位为1，则输出2^i } } return 0; } C 蓝与橙的简单位运算 难度 考点 2 位运算 题目分析 参考例题 C03-02。 将二进制码上的某几位替换成另外几位数字，可以采用如下做法（以样例的第二行为例）： 首先，将需要被替换的数位全部置 0 ，这一步可以通过按位与来计算 unsigned int num = 255, n = 4, key = 1; //num的二进制码为11111111，需要操作的位数为4~7四位 //我们可以将4~7位和0进行与运算，其他位和1进行与运算 unsigned int tmp = ~(15 &lt;&lt; n); //15的二进制码为1111,左移四位后变为11110000，取反后为00001111 num &amp;= tmp; //此时num的二进制码变为00001111，其他位被保留，4~7位变成0 然后，将替换上去的数字移动到对应的区域，这一步可以通过左移来实现 key &lt;&lt;= n; //此时key的二进制由0001变为00010000,4~7位为对应的二进制码，其余位为0 最后，进行按位或，由于 numnumnum 的其他位没有变化而 keykeykey 在其他位上皆为 0 ,因此按位或的结果与原二进制码相同，而需要替换的位为 0 与 keykeykey 的二进制码的按位或，得到的结果依然为 keykeykey 的二进制码。 num |= key; 示例代码 #include &lt;stdio.h&gt; int main() { unsigned int num, i, key; //不定组数据的各种输入方式还请各位牢记 while (scanf(&quot;%u%u%u&quot;, &amp;num, &amp;i, &amp;key) != EOF) { unsigned int tmp = ~(15 &lt;&lt; i); //15的二进制码为1111左移并取反之后为111……100001……11的形式 num &amp;= tmp; //进行按位与操作后，num需要修改的位的二进制码变为0，其余位不变 num |= (key &lt;&lt; i); //将替换上去的数字移动到对应的区域进行按位或，得到最终结果 printf(&quot;%u\\n&quot;, num); } return 0; } D 数字独舞会 难度 考点 1~2 数组，循环 题目分析 本题主要考察利用数组解决问题的能力。 首先，输入中提到 N≤106N \\leq 10^6N≤106，参考 Hint1，我们应该把数组大小设置在略大于 10610^6106，可取 100000510000051000005。 随后，当我们读取到一个未曾记录的数 iii 时，不妨令 a[i] = 1，标记数 iii 已经出现，并令不同数的总数 cnt 自增 1 ；当再次读取到数 iii​ 时，可以选择跳过该数。 当读取完所有数后，输出结果 cnt。 示例代码 #include &lt;stdio.h&gt; int a[1000005]; //大数组要开在main函数外面，否则可能发生栈溢出错误 int main() { int N, num, cnt = 0; //利用 num 变量储存输入的数 scanf(&quot;%d&quot;, &amp;N); for (int i = 0; i &lt; N; i++) { scanf(&quot;%d&quot;, &amp;num); if (a[num] == 0) //代表数 num 未出现过 { a[num] = 1; cnt++; //不同的数的总数加 1 } } printf(&quot;%d\\n&quot;, cnt); } E 位操作器 难度 考点 3 位操作 题目分析 对一个数的固定某些二进制位进行操作，例如当 l=0,r=2l=0,r=2l=0,r=2 时，对第 0,1,20,1,20,1,2 位进行操作，首先我们需要一个有且仅有第 0,1,20,1,20,1,2 二进制位为 111，其他位均为 000 的操作数 bit。怎样可以快速得到这样一个数呢？bit = (1LL &lt;&lt; (r + 1)) - (1LL &lt;&lt; l) 或者 bit = ((1LL &lt;&lt; (r - l + 1)) - 1) &lt;&lt; l（注意运算符优先级问题！没把握就多加括号！）当 l=0,r=2l=0,r=2l=0,r=2 时，bit 就是二进制下的 1000−1=1111000 - 1= 1111000−1=111。 获得了这样一个操作数后，如果需要的操作是置 0，则将操作数取反后与待操作数求与；如果是置 1，则将操作数与待操作数求或；如果是取反，则将操作数与待操作数求异或。 示例代码 #include &lt;stdio.h&gt; int main() { int op, l, r; long long n; scanf(&quot;%lld&quot;, &amp;n); while (scanf(&quot;%d%d%d&quot;, &amp;op, &amp;l, &amp;r) != EOF) { long long bit = (1LL &lt;&lt; (r + 1)) - (1LL &lt;&lt; l); // 操作数，记得用 1LL 和 long long if (op == 1) n &amp;= ~bit; else if (op == 2) n |= bit; else if (op == 3) n ^= bit; } printf(&quot;%lld&quot;, n); return 0; } 可能不少同学对于位运算的操作还很不熟练，所以也可以用数组来模拟整个过程。当然还是更推荐使用位运算的写法。 #include &lt;stdio.h&gt; int main() { int arr[65] = {}, op, l, r, i; long long n, ans = 0; scanf(&quot;%lld&quot;, &amp;n); for (i = 0; n &gt; 0; i++) // 初始化数组使它单独存储待操作数的每一位 { if (n &amp; 1) arr[i] = 1; n &gt;&gt;= 1; } while (scanf(&quot;%d%d%d&quot;, &amp;op, &amp;l, &amp;r) != EOF) { if (op == 1) { for (i = l; i &lt;= r; i++) { arr[i] = 0; } } else if (op == 2) { for (i = l; i &lt;= r; i++) { arr[i] = 1; } } else { for (i = l; i &lt;= r; i++) { arr[i] ^= 1; // 这里就是0变成1，1变成0，当然也能写成 arr[i] = 1 - arr[i] } } } for (i = 0; i &lt; 62; i++) // 将操作完毕的数组重新组合成一个数 { if (arr[i] == 1) ans |= 1LL &lt;&lt; i; } printf(&quot;%lld&quot;, ans); return 0; } F 字母频率 难度 考点 2~3 数据类型 题目分析 使用多组输入输出的方法，依次读入字符，利用语句 c1 &gt;= 'a' &amp;&amp; c1 &lt;= 'z' 判断该字符是否是字母，利用语句 c1 + 'a' - 'A' 得到某一大写字母转换为对应的小写字母。 示例代码 #include &lt;stdio.h&gt; int main(){ int n = 0, m = 0; char c; scanf(&quot;%c&quot;, &amp;c); char c1; while (~scanf(&quot;%c&quot;, &amp;c1)) { if (c1 == c || c1 + 'a' - 'A' == c){ // 判断一个字符是否是需要统计的字母 m++; n++; } else if ((c1 &gt;= 'a' &amp;&amp; c1 &lt;= 'z') || (c1 &gt;= 'A' &amp;&amp; c1 &lt;= 'Z')) // 判断一个字符是否是字母 n++; } printf(&quot;%d\\n&quot;, m); printf(&quot;%.2lf%%&quot;, 100.0 * m / n); } Author: SiSi G czx 的植树节 难度 考点 2 整数除法，模运算 题目分析 考虑求解方程 l≤kx≤rl \\leq kx \\leq rl≤kx≤r 的整数解个数，不难发现，结果一定为 ⌊rk⌋−⌈lk⌉+1\\lfloor \\frac{r}{k} \\rfloor - \\lceil \\frac{l}{k} \\rceil + 1⌊kr​⌋−⌈kl​⌉+1. 进而，要解决这个问题，只需求出 ⌊rk⌋\\lfloor \\frac{r}{k} \\rfloor⌊kr​⌋ 和 ⌈lk⌉\\lceil \\frac{l}{k} \\rceil⌈kl​⌉ 即可。 由于在 C 语言中，整数的除法结果为 向零取整，并且整数的取模结果符号与被除数相同（如 -10 % 3 = -1），因此在进行上取整下取整操作时都需要分类讨论许多因素。 考虑计算 ⌊rk⌋\\lfloor \\frac{r}{k} \\rfloor⌊kr​⌋。 r mod k == 0.⌊rk⌋=rk\\lfloor \\frac{r}{k} \\rfloor = \\frac{r}{k}⌊kr​⌋=kr​. r mod k != 0. r &gt; 0.此时整数除法向零取整，即向下取整。计算 r / k 即可。 r &lt; 0.此时整数除法向上取整，此时的结果为 r / k - 1。 考虑计算 ⌈lk⌉\\lceil \\frac{l}{k} \\rceil⌈kl​⌉。 l mod k == 0.⌈lk⌉\\lceil \\frac{l}{k} \\rceil⌈kl​⌉. l mod k != 0. l &gt; 0.此时整数除法向零取整，即向下取整。计算 l / k + 1 即可。 l &lt; 0.此时整数除法向上取整，此时的结果为 r / k。 进而可以求解出题目的答案。 另外，我们还有一些巧妙的方法可以更快的解决这个问题。 对于上下取整操作，我们可以不需要做繁杂的分类讨论，直接做除法后，比较结果与除数相乘后得到的数与被除数的大小，再决定是否对结果加减一。 对于这道题而言，我们考虑到结果即为 [l,r][l, r][l,r] 内包含的 kxkxkx 点的个数，它的结果一定是 ⌊r−lk⌋\\lfloor \\frac{r-l}{k} \\rfloor⌊kr−l​⌋ 或 ⌊r−lk⌋+1\\lfloor \\frac{r-l}{k} \\rfloor + 1⌊kr−l​⌋+1。考虑什么时候需要 +1+1+1。实际上，当 lll 点离它最近的 kxkxkx 点距离和 rrr 点离它最近的 kxkxkx 点距离之和小于 kkk 时，需要 +1+1+1。求解这两个距离可以通过取模操作完成。 示例代码 1 #include &lt;stdio.h&gt; // l-a&lt;=k*m&lt;=r-a int main() { int T; scanf(&quot;%d&quot;, &amp;T); while (T--) { long long a, m, l, r; scanf(&quot;%lld%lld%lld%lld&quot;, &amp;a, &amp;m, &amp;l, &amp;r); l -= a, r -= a; if (r % m != 0) r = r &gt;= 0 ? r / m : r / m - 1; else r /= m; if (l % m != 0) l = l &gt;= 0 ? l / m + 1 : l / m; else l /= m; if (r &gt;= l) printf(&quot;%lld\\n&quot;, r - l + 1); else puts(&quot;0&quot;); } return 0; } 示例代码 2 #include &lt;stdio.h&gt; #define ll long long int main() { int T; ll a, m, l, r, max, min; scanf(&quot;%d&quot;, &amp;T); while (T--) { scanf(&quot;%lld%lld%lld%lld&quot;, &amp;a, &amp;m, &amp;l, &amp;r); max = (r - a) / m; if (max * m &gt; r - a) max--; min = (l - a) / m; if (min * m &lt; l - a) min++; printf(&quot;%lld\\n&quot;, max - min + 1); } return 0; } 示例代码 3 #include &lt;stdio.h&gt; int main() { int T; scanf(&quot;%d&quot;, &amp;T); while(T--) { long long a, m, l, r; scanf(&quot;%lld%lld%lld%lld&quot;, &amp;a, &amp;m, &amp;l, &amp;r); long long al_mod = ((a - l) % m + m) % m; //即(a-l) mod m，为保证是非负数因此+m再%m long long rl_mod = ((r - l) % m + m) % m; //即(r-l) mod m //当且仅当al_mod&lt;=rl_mod时结果(r-l)/m需要再+1 printf(&quot;%lld\\n&quot;, (r - l) / m + (al_mod &lt;= rl_mod)); } return 0; } H 洋洋的处理器2 难度 考点 5 数论，取余 大致题意 给定一个 [0,65535][0,65535][0,65535] 之内未知的初始值和一段指令序列，指令是每次可以将这个数字加上一个 [0,65535][0,65535][0,65535] 当中的数或乘上一个 [0,65535][0,65535][0,65535] 当中的数字，问对于所有的初始值，在执行完成指令序列过后，最终的数字有多少种不同的情况？ 题目分析 首先假设寄存器当中的数字为 RRR。我们先把取模操作不看，因为加法和乘法与取模运算互不影响，每次执行指令后都取模和每次不取模最后再取模在数学上是完全相等的。我们发现只有加法操作和乘法操作，因此答案一定可以写成 a×R+ba \\times R + ba×R+b 的形式，其中 aaa 和 bbb 是两个整数。而对这个式子无论执行哪种指令，又都可以转换为该形式: 加法指令：(a×R+b)+X=a×R+(b+X)(a \\times R + b) + X = a \\times R + ( b + X )(a×R+b)+X=a×R+(b+X) 乘法指令：(a×R+b)×X=(a×X)×R+(b×X)(a \\times R + b) \\times X = (a\\times X) \\times R + ( b \\times X )(a×R+b)×X=(a×X)×R+(b×X) 因此我们知道，如果寄存器初始值为 KKK，那么执行完所有指令过后可以写成 (a×K+b) mod 65536(a\\times K + b)\\bmod 65536(a×K+b)mod65536 的形式。显然地，对于初始时也应该写成该形式，即 K=1×K+0K = 1\\times K +0K=1×K+0 。从上面的两个式子可以得出，在 KKK 未知的时候，我们可以通过只计算 a,ba,ba,b 的变化，从而让操作过程无视 KKK，直接得到 a×K+ba\\times K + ba×K+b 的结果，也就是算出 a,ba,ba,b 。 那么题目转化成了，已知 a,ba,ba,b ，让 KKK 取遍 [0,65535][0,65535][0,65535] 当中的所有整数， (a×K+b)mod 65536(a\\times K + b)\\mod 65536(a×K+b)mod65536 有几种不同的值？我们假设结果的值的集合为 SSS ，那么就是求 SSS 的大小。 首先我们观察出，这个 +b+b+b 在数轴上来看只是把所有的 a×Ka \\times Ka×K 整体平移了 bbb 个单位得到 SSS，而平移是不会改变集合的大小的。因此 ∣S∣=∣{a×K∣K∈[0,65535]}∣|S| = | \\lbrace a\\times K| K \\in [0,65535]\\rbrace |∣S∣=∣{a×K∣K∈[0,65535]}∣ 。到了这一步，我们发现答案和 bbb 无关，再观察上面两个指令，RRR 的系数 aaa 又只会被乘法指令改变，因此实际上所有的加法操作，也就是操作 000 对答案都没用了，我们只需要通过操作 111 算出来 aaa 即可。 然后，现在已知 aaa ，怎么算不同结果的个数呢？由于开头提到加法乘法和取模互不影响，因此我们可以让 aaa 每次指令对 655366553665536 取模，这样让 aaa 变到 [0,65535][0,65535][0,65535] 之内。如果大家知道裴蜀定理，那么可以直接得到 (a×K) mod 65536(a\\times K) \\bmod 65536(a×K)mod65536 的不同个数为 65536gcd⁡(65536,a)\\frac{65536}{\\gcd(65536,a)}gcd(65536,a)65536​ 个，其中 gcd⁡\\gcdgcd 是最大公约数。如果不知道，也可以观察规律： 65536=21665536 = 2^{16}65536=216，分解质因子后全是 222。 思路启发：如果 aaa 是奇数，那么 KKK 取遍[0,65535][0,65535][0,65535] ，那么答案画一画知道也能够取遍 [0,65535][0,65535][0,65535]，具体就是奇数 K×aK \\times aK×a 还是奇数，偶数 K×aK \\times aK×a 还是偶数，不同奇数 ×a\\times a×a 仍然不同，偶数让 KKK 和 655366553665536 同时除以 222 ，再重复刚刚的操作递归下去。就可以得到 KKK 和 (a×K) mod 65536(a\\times K) \\bmod 65536(a×K)mod65536 是一一对应的。 更进一步：如果 aaa 的质因子当中有 ppp 个 222 ，那么 (a×K) mod 65536(a \\times K) \\bmod 65536(a×K)mod65536 也一定是 2p2^p2p 的倍数（因为 655366553665536也是它的倍数！），供 655362p\\frac{65536}{2^p}2p65536​ 个不同的值。而 KKK 取遍 [0,65535][0,65535][0,65535] ，因此这么多结果的值都能够取到。 极限条件：对于 aaa 的除了 222 的别的质因子 ttt ，&quot;更进一步&quot;当中 tpt^ptp 的性质不能够保证，因为 655366553665536 没有除了 222 的因子。用“思路启发”当中的方法，我们可以发现这样的因子不会对结果产生影响。至此得到结论：答案为 655362p\\frac{65536}{2^p}2p65536​，其中 ppp 为 aaa 当中质因子 222 的个数。 注：如果大家觉得上面四句话还是很费解（确实很费解，因为这部分直接想还是很考验数学直觉的），或者说想比较透彻简单地理解前面直接得到的答案，那么大家可以去了解一下裴蜀定理，就可以不用看上面四句了。 示例代码 #include &lt;stdio.h&gt; #define lowbit(x) (x &amp; -x) int main(void) { int n, a, x, ans = 65536; scanf(&quot;%d&quot;, &amp;n); while (n--) { scanf(&quot;%d%d&quot;, &amp;a, &amp;x); if (a == 1)//如果是乘法操作 { ans /= lowbit(x); /* lowbit取出x的二进制表示当中最低的那一位和后面的所有0（大家可以根据上面的#define的定义手动画一画加深理解），也就是取出x分解质因子后2的幂。 如：lowbit(6)=2, lowbit(8)=8, lowbit(15)=1, lowbit(30)=2 把每次的x乘起来变成a再与65536取最大公约数，和用65536每次除以x当中所有2的质因子没有什么区别。 */ } } if (ans == 0) ans = 1; /* 说明a能被65536整除，那么指令序列执行完一定是0，答案为一种。 换句话说gcd(a,65536)=65536, 65536/65536=1。 */ printf(&quot;%d&quot;, ans); return 0; } I 仙人的异或数组 难度 考点 5 异或，贪心 题目分析 ​ 对于位运算求最值的问题，我们按位考虑，即优先满足高位为 1 ​ 再由异或的性质，当 ai,i=1⋯na _ i, i = 1 \\cdots nai​,i=1⋯n 中有奇数个第 jjj 位为 1 时即可使异或和此位为 1 ，这里可以想到一个很明显的贪心策略：因为 aaa 数组的数可以为 0 ，所以我们对于当前考虑的位，只需要使其中一个 aia _ iai​ 的当前位为 1 ，其他数的当前位均设为 0 即可 ​ 因此，我们从最高位开始向低位考虑，若当前位 bbb 有多个位置可以取到，即满足 a[i]≥(1&lt;&lt;b)a[i] \\geq (1 &lt;&lt; b)a[i]≥(1&lt;&lt;b) 那么我们选择哪个位置？ 若满足条件的 iii 仅有一个，那么由异或的性质，一定要取 若满足条件的 iii 有多个，我们任意选择一个位置取，那么，易知，剩下的数一定可以取到 (1&lt;&lt;b)−1(1 &lt;&lt; b) - 1(1&lt;&lt;b)−1 ​ 因此，我们最终的贪心策略就得出了：**从高到低按位考虑，对于当前位 bbb ，在数组 aaa 中有 aia _ iai​ 可以取，即满足 ai≥(1&lt;&lt;b)a _ i \\geq (1 &lt;&lt; b)ai​≥(1&lt;&lt;b) ，那么我们优化答案，并将 aia _ iai​​ 进行更新 示例代码 1 #include &lt;stdio.h&gt; #define ll long long #define maxn 100005 #define BIT 35 ll n, arr[maxn]; ll ans; int main() { while(scanf(&quot;%lld&quot;, &amp;n) != EOF) { ans = 0; for(int i = 1; i &lt;= n; i++) scanf(&quot;%lld&quot;, &amp;arr[i]); for(int b = BIT; b &gt;= 0; b--) { for(int i = 1; i &lt;= n; i++) { if((1ll &lt;&lt; b) &lt;= arr[i]) { arr[i] -= (1ll &lt;&lt; b); ans += (1ll &lt;&lt; b); break; } } } printf(&quot;%lld\\n&quot;, ans); } return 0; } 示例代码 2 另一种思路 #include &lt;stdio.h&gt; int main() { int n; while(~scanf(&quot;%d&quot;, &amp;n)) { int cnt[32] = {}; //cnt[i]记录有多少个x第i位为1 while(n--) { int x; scanf(&quot;%d&quot;, &amp;x); for(int i = 0; i &lt; 31; ++i) cnt[i] += x &gt;&gt; i &amp; 1; } int ans = 0; for(int i = 30; i &gt;= 0; --i) { if(cnt[i] &gt;= 1) ans |= 1 &lt;&lt; i; if(cnt[i] &gt; 1) { ans += (1 &lt;&lt; i) - 1; break; } } printf(&quot;%d\\n&quot;, ans); } return 0; } J 异或求和 难度 考点 6 异或、拆位、计数 题目分析 记 f(x)=∑k=0x−1(m⊕k)f(x) = \\sum\\limits_{k=0}^{x-1}(m\\oplus k)f(x)=k=0∑x−1​(m⊕k)（特别地，记 f(0)=0f(0)=0f(0)=0），则原问题等价于求 f(r+1)−f(l)f(r+1)-f(l)f(r+1)−f(l)​。 因此我们仅需找到方法计算 f(x)f(x)f(x) 即可。 在此需要利用拆位的思想，即将二进制的每一位分开来逐位分析，最后再将每一位的结果计算总和。具体数学推导如下。 本题中数据均不大于 10910^9109，均可用 303030 位二进制数表示。 设 m=(m29m28⋯m1m0)2=∑i=029mi×2im=(m_{29}m_{28}\\cdots m_1m_0)_2=\\sum\\limits_{i=0}^{29}m_i\\times 2^im=(m29​m28​⋯m1​m0​)2​=i=0∑29​mi​×2i，其中 (⋯ )2(\\cdots)_2(⋯)2​ 表示括号内为二进制数。 设 k=(k29k28⋯k1k0)2=∑i=029ki×2ik=(k_{29}k_{28}\\cdots k_1k_0)_2=\\sum\\limits_{i=0}^{29}k_i\\times 2^ik=(k29​k28​⋯k1​k0​)2​=i=0∑29​ki​×2i。 则 m⊕k=∑i=029(mi⊕ki)×2im \\oplus k = \\sum\\limits_{i=0}^{29}(m_i\\oplus k_i)\\times 2^im⊕k=i=0∑29​(mi​⊕ki​)×2i。 代入 f(x)f(x)f(x) 表达式并交换求和次序可得： f(x)=∑k=0x−1(m⊕k)=∑k=0x−1[∑i=029(mi⊕ki)×2i]=∑i=0292i×[∑k=0x−1(mi⊕ki)]f(x)=\\sum\\limits_{k=0}^{x-1}(m\\oplus k) \\\\ = \\sum\\limits_{k=0}^{x-1}\\left[\\sum\\limits_{i=0}^{29}(m_i\\oplus k_i)\\times 2^i\\right]\\\\ = \\sum\\limits_{i=0}^{29}2^i\\times \\left[\\sum\\limits_{k=0}^{x-1}(m_i\\oplus k_i)\\right] f(x)=k=0∑x−1​(m⊕k)=k=0∑x−1​[i=0∑29​(mi​⊕ki​)×2i]=i=0∑29​2i×[k=0∑x−1​(mi​⊕ki​)] 问题的难点就转化为了求 fi(x)=∑k=0x−1(mi⊕ki)f_i(x)=\\sum\\limits_{k=0}^{x-1}(m_i\\oplus k_i)fi​(x)=k=0∑x−1​(mi​⊕ki​)。 即，我们需要统计区间 [0,x−1][0,x-1][0,x−1] 内所有自然数中，二进制的第 iii 位（最低位为第 000 位）与 mim_imi​​​ 不同的自然数的个数。 符号 ⌊⋯ ⌋\\lfloor\\cdots\\rfloor⌊⋯⌋ 表示向下取整， mod \\bmodmod 表示取模运算（即求余运算）。 以 i=1,mi=1,x=11i=1, m_i = 1, x=11i=1,mi​=1,x=11 为例，自然数 0,1,2,3,4,5,6,7,8,9,100, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100,1,2,3,4,5,6,7,8,9,10 的二进制第 111 位为 0,0,1,1,0,0,1,1,0,0,10, 0, 1, 1, 0, 0, 1, 1, 0, 0, 10,0,1,1,0,0,1,1,0,0,1。可见，前 4×24\\times 24×2 个数中有 ⌊114⌋×2=4\\left\\lfloor\\frac{11}4\\right\\rfloor\\times 2=4⌊411​⌋×2=4 个 000，后三个数中有 222 个 000​​。 以 i=2,mi=1,x=11i=2, m_i = 1, x=11i=2,mi​=1,x=11 为例，自然数 0,1,2,3,4,5,6,7,8,9,100, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100,1,2,3,4,5,6,7,8,9,10 的二进制第 111 位为 0,0,0,0,1,1,1,1,0,0,00, 0, 0, 0, 1, 1, 1, 1, 0, 0, 00,0,0,0,1,1,1,1,0,0,0。可见，前 888 个数中有 ⌊118⌋×4=4\\left\\lfloor\\frac{11}8\\right\\rfloor\\times 4=4⌊811​⌋×4=4 个 000，后三个数中有 11 mod 8=311 \\bmod 8 = 311mod8=3 个 000。 一般地，区间 [0,x−1][0, x-1][0,x−1] 中，前 ⌊x2i+1⌋×2i+1\\left\\lfloor\\frac x{2^{i+1}}\\right\\rfloor\\times{2^{i+1}}⌊2i+1x​⌋×2i+1 个数中第 iii 位为 000 的数有 ⌊x2i+1⌋×2i\\left\\lfloor\\frac x{2^{i+1}}\\right\\rfloor\\times{2^{i}}⌊2i+1x​⌋×2i 个，后 x−⌊x2i+1⌋×2i+1=x mod 2i+1x-\\left\\lfloor\\frac x{2^{i+1}}\\right\\rfloor\\times{2^{i+1}}=x\\bmod2^{i+1}x−⌊2i+1x​⌋×2i+1=xmod2i+1 个数中第 iii 位为 000 的数有 min⁡(x mod 2i+1,2i)\\min(x\\bmod2^{i+1}, 2^i)min(xmod2i+1,2i) 个，共计 ⌊x2i+1⌋×2i+min⁡(x mod 2i+1,2i)\\left\\lfloor\\frac x{2^{i+1}}\\right\\rfloor\\times{2^{i}}+\\min(x\\bmod2^{i+1}, 2^i)⌊2i+1x​⌋×2i+min(xmod2i+1,2i) 个。 即，若 mi=1m_i=1mi​=1，则 fi(x)=∑k=0x−1(mi⊕ki)=⌊x2i+1⌋×2i+min⁡(x mod 2i+1,2i)f_i(x)=\\sum\\limits_{k=0}^{x-1}(m_i\\oplus k_i)=\\left\\lfloor\\frac x{2^{i+1}}\\right\\rfloor\\times{2^{i}}+\\min(x\\bmod2^{i+1}, 2^i)fi​(x)=k=0∑x−1​(mi​⊕ki​)=⌊2i+1x​⌋×2i+min(xmod2i+1,2i)； 若 mi=0m_i=0mi​=0，则 fi(x)=x−[⌊x2i+1⌋×2i+min⁡(x mod 2i+1,2i)]=⌊x2i+1⌋×2i+max⁡[(x mod 2i+1)−2i,0]f_i(x)=x-\\left[\\left\\lfloor\\frac x{2^{i+1}}\\right\\rfloor\\times{2^{i}}+\\min(x\\bmod2^{i+1}, 2^i)\\right]=\\left\\lfloor\\frac x{2^{i+1}}\\right\\rfloor\\times{2^{i}}+\\max[(x\\bmod2^{i+1})- 2^i, 0]fi​(x)=x−[⌊2i+1x​⌋×2i+min(xmod2i+1,2i)]=⌊2i+1x​⌋×2i+max[(xmod2i+1)−2i,0]​​。 按照上述 fi(x)f_i(x)fi​(x) 的公式，计算 f(x)=∑i=0292i×fi(x)f(x)=\\sum\\limits_{i=0}^{29}2^i\\times f_i(x)f(x)=i=0∑29​2i×fi​(x)，进而得到 f(r+1)−f(l)f(r+1)-f(l)f(r+1)−f(l)​，即可得到最终的答案。 注意使用合适的变量类型，该用 long long 的时候要用 long long。 示例代码 1 #include&lt;stdio.h&gt; int main() { int T; scanf(&quot;%d&quot;, &amp;T); while(T--) { int m, l, r; scanf(&quot;%d%d%d&quot;, &amp;m, &amp;l, &amp;r); long long f1 = 0, f2 = 0; // 计算f(l)，存入f1 int x = l; for(int i = 0; i &lt; 30; ++i) { // 计算f_i(x) long long f_i = x / (1 &lt;&lt; i + 1) * (1 &lt;&lt; i); if(m &gt;&gt; i &amp; 1) // m_i为1时 { // min[x mod 2^(i+1), 2^i] if(x % (1 &lt;&lt; i + 1) &gt; 1 &lt;&lt; i) f_i += 1 &lt;&lt; i; else f_i += x % (1 &lt;&lt; i + 1); } else // m_i为0时 { // max[x mod 2^(i+1) - 2^i, 0] if(x % (1 &lt;&lt; i + 1) &gt; 1 &lt;&lt; i) f_i += x % (1 &lt;&lt; i + 1) - (1 &lt;&lt; i); } f1 += f_i &lt;&lt; i; // 2^i*f_i(x) } // 计算f(r+1)，存入f2 x = r + 1; for(int i = 0; i &lt; 30; ++i) { // 另一种计算f_i(x)的方法，与上面的可以互换 long long f_i = x / (1 &lt;&lt; i + 1) * (1 &lt;&lt; i); // max[x mod 2^(i+1) - 2^i, 0] if(x % (1 &lt;&lt; i + 1) &gt; 1 &lt;&lt; i) f_i += x % (1 &lt;&lt; i + 1) - (1 &lt;&lt; i); //此时为m_i为0时的结果 if(m &gt;&gt; i &amp; 1) // 若m_i为1 f_i = x - f_i; f2 += f_i &lt;&lt; i; // 2^i*f_i(x) } printf(&quot;%lld\\n&quot;, f2 - f1); } return 0; } 示例代码 2 在第五章学过函数之后我们可以使用函数来简化代码，大家可以之后再来回顾。 #include&lt;stdio.h&gt; long long f(int x, int m) { long long fx = 0; for(int i = 0; i &lt; 30; ++i) { long long f_i = x / (1 &lt;&lt; i + 1) * (1 &lt;&lt; i); if(x % (1 &lt;&lt; i + 1) &gt; 1 &lt;&lt; i) f_i += x % (1 &lt;&lt; i + 1) - (1 &lt;&lt; i); if(m &gt;&gt; i &amp; 1) f_i = x - f_i; fx += f_i &lt;&lt; i; } return fx; } int main() { int T; scanf(&quot;%d&quot;, &amp;T); while(T--) { int m, l, r; scanf(&quot;%d%d%d&quot;, &amp;m, &amp;l, &amp;r); printf(&quot;%lld\\n&quot;, f(r + 1, m) - f(l, m)); } return 0; } - End - ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"C2 - Solution-23航C","link":"https://github.pansis.site/post/C2 - Solution-23航C/","stats":{"text":"32 min read","time":1893000,"words":6705,"minutes":32},"dateFormat":"2024-03-14"},{"content":"A 奇异的位运算2024 F=A‾B‾+AB=A‾B+AB‾‾F=\\overline A \\overline B +AB=\\overline {\\overline A B+A \\overline B}F=AB+AB=AB+AB​ 按位异或 A B A^B 0 0 0 0 1 1 1 0 1 1 1 0 代码 #include &lt;stdio.h&gt; int main() { unsigned a, b; while (scanf(&quot;%u%u&quot;, &amp;a, &amp;b) != EOF) { printf(&quot;%u\\n&quot;, ~(a ^ b)); } return 0; } B 小宇的基物实验物资 提取某一位 if ((1u&amp;(b&gt;&gt;i))!=0) 代码 #include &lt;stdio.h&gt; int main() { unsigned int b; scanf(&quot;%u&quot;,&amp;b); for (int i = 0; i &lt; 32; ++i) { if ((1u&amp;(b&gt;&gt;i))!=0) { printf(&quot;%u &quot;,1u&lt;&lt;i); } } } C 蓝与橙的简单位运算 #include&lt;stdio.h&gt; int main() { int num,n,key; while ((scanf(&quot;%d%d%d&quot;,&amp;num,&amp;n,&amp;key))!=EOF) { num&amp;=~(((1&lt;&lt;(4))-1)&lt;&lt;n); num|=(key&lt;&lt;n); printf(&quot;%d\\n&quot;,num); } } 多保存一些位运算的代码模板，上机可以直接用 long long mian123(long long a,int n,int x) //替换二进制特定位的值 //a为要转化的数字，n为替换的值（0或1），x为位数 { if (n==0) a &amp;= (~(1 &lt;&lt; x)); //将a的二进制第x位替换为0 else a|=(1&lt;&lt;x); //将a的二进制第x位替换为1 return a; } long long mian1234(long long x,int n) //提取二进制的特定位 //x为要转化的数字，n为位数 { return x&amp;(1&lt;&lt;n); //取出x的第m位，保存在a中（若为1，则a=2^m,否则a=0） } unsigned int mian2(unsigned int a1,int begin,int end,int n) //替换二进制中特定连续位置，begin和end从0位开始，n为要替换的数，a1为被替换数 { a1&amp;=~(((1&lt;&lt;(end-begin+1))-1)&lt;&lt;begin); a1|=(n&lt;&lt;begin); return a1; } D 数字独舞会 全局数组与局部数组 #include &lt;stdio.h&gt; int s[1000005]; int main() { int N, temp, cnt = 0; scanf(&quot;%d&quot;, &amp;N); for (int i = 0; i &lt; N; i++) { scanf(&quot;%d&quot;, &amp;temp); if (s[temp]==1) continue; else { cnt +=1; s[temp] = 1; } } printf(&quot;%d\\n&quot;, cnt); } E 位操作器 建议每次操作1位 #include&lt;stdio.h&gt; long long zhiling(long long n,int l, int r) { for (int i = l; i &lt;= r; ++i) { n&amp;=(~(1ll&lt;&lt;i)); } return n; } long long zhi1(long long n,int l, int r) { for (int i = l; i &lt;= r; ++i) { n|=((1ll&lt;&lt;i)); } return n; } long long fanzhuan(long long n,int l, int r) { for (int i = l; i &lt;= r; ++i) { if (((n&gt;&gt;i)&amp;1)==0) n|=((1ll&lt;&lt;i)); else n&amp;=(~(1ll&lt;&lt;i)); } return n; } int main() { long long n; int op,l,r; scanf(&quot;%lld&quot;,&amp;n); while ((scanf(&quot;%d%d%d&quot;,&amp;op,&amp;l,&amp;r))!=EOF) { if (op==1) n= zhiling(n,l,r); if (op==2) n=zhi1(n,l,r); if(op==3) n= fanzhuan(n,l,r); } printf(&quot;%lld&quot;,n); } F 字母频率 dev调试 #include &lt;stdio.h&gt; int main() { char a; scanf(&quot;%c&quot;,&amp;a); int num=0;int anum=0; char s; getchar(); while ((scanf(&quot;%c&quot;,&amp;s))!=EOF) { if (s==a || s-a=='a'-'A' ||s-a=='A'-'a') anum++; if (s!=' ' &amp;&amp; s!='\\n') num++; } printf(&quot;%d\\n%.2f%%&quot;,anum,1.0*100*anum/num); } G czx 的植树节 #include&lt;stdio.h&gt; int main() { int t; long long a,m,l,r; scanf(&quot;%d&quot;,&amp;t); while (t--) { long long num=0; scanf(&quot;%lld%lld%lld%lld&quot;,&amp;a,&amp;m,&amp;l,&amp;r); long long k1=(l-a)/m; long long k2=(r-a)/m; if (k2*m&gt;r-a) k2--; if (k1*m&lt;l-a) k1++; printf(&quot;%lld\\n&quot;,k2-k1+1); } } G ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"C2 讲解-23航C","link":"https://github.pansis.site/post/C2 讲解-23航C/","stats":{"text":"5 min read","time":270000,"words":813,"minutes":5},"dateFormat":"2024-03-14"},{"content":"一、深度神经网络 1、专用芯片设计流程 2、DNN常见种类 全连接神经网络，又称多层感知器 (MLP) 卷积神经网络 (CNN) ，具有权重共享的稀疏连接 循环神经网络 (RNN) 长短期记忆 (LSTM) ，反馈+ 存储 3、卷积层计算公式 4、激活函数 5、矩阵乘法分块 优化缓存访问、提升数据复用 二、脉动阵列 1、第一种形式（结果不动，两个输入流动） 单元结构 2、第二种形式（结果和一个输入流动，其中一个输入不动） 3、代码 第一种形式见work1_2 第二种形式见work1 ","tags":[{"index":-1,"name":"飞行器智能处理器设计实验","slug":"fxqznclqsjsy","used":true,"link":"https://github.pansis.site/tag/fxqznclqsjsy/"}],"title":"1.1 脉动阵列","link":"https://github.pansis.site/post/1.1 脉动阵列/","stats":{"text":"1 min read","time":36000,"words":169,"minutes":1},"dateFormat":"2024-03-11"},{"content":"一、飞行程序 1、定义 通过 设计攻角 的程序角，得到主动段飞行时 俯仰角 的变化规律。 2、性能分析 终端速度倾角相同 ，不同的飞行程序对应不同的技战术性能： 二、飞行程序设计基本原则 1、基本原则 1、垂直起飞 垂直起飞，可以使发射台设计的结构简单，同时也使火箭在起飞时保持稳定 垂直起飞段的时间的长短一般至少保证延续到发动机进入额定工作状态的时刻，与此同时，控制机构也能正常地控制火箭姿态实现转弯（提供足够的控制力矩）。 2、限制火箭转弯时的法向过载（重力转弯） 过载公式： 空气力矩以及由气动力造成的法向过载与qα成正比 重力产生的法向过载成反比 在跨音速及其整个具有大动压头的转弯段弹道上攻角为零或尽可能地小，使火箭 只在重力法向分量 − mgcos 作用下转弯（转弯角速度与重力分量平衡） 法向运动学公式 转弯段，不能一直靠攻角转弯，会降低燃料最优性 重力转弯可以减小推力偏离速度方向的攻角速度损失 3、程序俯仰角变化应连续，角速度与角加速度要有限制 若程序俯仰角𝜑_{𝑝𝑟}(𝑡)间断，则角速度\\dot𝜑_{𝑝𝑟}(𝑡)无穷大，从而会使法向过载过大 同时，\\ddot𝜑_{𝑝𝑟(𝑡)}过大，惯性力矩变大，有可能使控制机构提供不了所需的控制力矩。 4、保证可靠的分离条件与合适的再入条件 分离期间要保证姿态尽可能的稳定（常值姿态角分离），目标射程对弹道倾角 的约束等。 三、主动段弹道的三个阶段 1、三个阶段 根据飞行程序的基本原则，将主动段弹道分成三段：垂直上升段、转弯段、 瞄准段 2、垂直上升段 1、主要参数：垂直段结束的时间 t1t_1t1​ ​ 过小：姿态控制能力不足 过大：转弯所需过载升高 2、公式： 3、计算垂直上升段高度 3、转弯段设计 1、两个阶段（先增大后减小，然后保持在0左右） 𝑡1𝑡_1t1​ −𝑡2𝑡_2t2​为有攻角的转弯，应在气动力急剧变化的跨音速之前结束，以 减少气动载荷和气动干扰 ，在马赫数为0.7-0.9时使攻角减小为零； 𝑡2𝑡_2t2​ −𝑡3𝑡_3t3​ 为高动压区，只依靠重力的法向分量进行转弯，即重力转弯。 2、经验公式1 公式： 参数： αmα_mαm​为音速段上攻角绝对值的最大值 ​ tmt_mtm​为攻角到达最大值的时间 3、经验公式2 参数：k αmα_mαm​为音速段上攻角绝对值的最大值 ​ 对应的 tm=t1+ln2/kt_m=t_1+ln2/ktm​=t1​+ln2/k(求导可得) 与【经验公式1】最大的区别在于𝑡2时刻的攻角不为0， 需要调整𝑡𝑚使𝑡2时刻攻角足够小。 4、设计实例 4、瞄准段飞行程序选择 基本流程 ","tags":[{"index":10,"name":"制导与控制原理","slug":"zdykzyl","used":true,"link":"https://github.pansis.site/tag/zdykzyl/"}],"title":"1.5 大气层内飞行程序设计","link":"https://github.pansis.site/post/1.5 大气层内飞行程序设计/","stats":{"text":"3 min read","time":174000,"words":824,"minutes":3},"dateFormat":"2024-03-08"},{"content":"C1 - Solution A 愿此行，终抵群星 难度 考点 1 输出内容 使用 printf 输出字符串，直接复制以下代码即可通过。 #include &lt;stdio.h&gt; int main() { printf(&quot;May this journey lead us starward.&quot;); return 0; } B 求矩形面积123456 难度 考点 1 输入输出，浮点数 题目分析 本题需要先读取四个浮点数，即 scanf(&quot;%lf%lf%lf%lf&quot;, &amp;xa, &amp;ya, &amp;xb, &amp;yb); 需要注意两点，一个是浮点数的读取要用%lf，另一个是在scanf 中被赋值的变量需要加取址符&amp;。 将两点坐标读入后计算矩形面积 (xb - xa) * (yb - ya) ,并用 printf 输出，保留两位小数用 %.2f。 在本课程中，为了保证计算精度，浮点数一律使用 double，请勿使用 float。 示例代码 #include &lt;stdio.h&gt; int main() { double xa, ya, xb, yb; scanf(&quot;%lf%lf%lf%lf&quot;, &amp;xa, &amp;ya, &amp;xb, &amp;yb); printf(&quot;%.2f&quot;, (xb - xa) * (yb - ya)); //先计算出(xb-xa)*(yb-ya)的值然后输出。 return 0; } C 整除div 难度 考点 1 整除 题目分析 本题涉及到了不定组输入。不定组输入是指输入组数不确定，程序必须在输入结束后结束。 本题已给出不定组输入的模板，即 while (scanf(&quot;%d%d&quot;, &amp;a, &amp;b) != EOF) { //读取了两个int整数，分别存入变量a，b中 //下面填写相应的处理代码 } 上述循环代表每次读入两个 int 整数，赋值给变量 a、b 后执行循环内部的代码，然后继续读取两个 int 整数，赋值给变量 a、b ，进行下一轮循环。直至scanf读取不到任何数字，返回 EOF 导致循环条件 scanf() != EOF 不成立，跳出循环。 示例代码 #include &lt;stdio.h&gt; int main() { int a,b; while (scanf(&quot;%d%d&quot;, &amp;a, &amp;b) != EOF) { if (b == 0) printf(&quot;Cann0t be divided by 0\\n&quot;); //输出的字符串能直接复制的，绝不手动输入 else printf(&quot;%d\\n&quot;,a/b); } return 0; } 补充说明 scanf 返回值解释 scanf 函数在执行时除了读取输入赋值给变量以外，还会返回一个 int 型的值：如果成功，该函数返回成功匹配和赋值的个数；如果到达文件末尾或发生读错误，则返回 EOF。 可见，如果 scanf 在读入过程中遇到文件末尾（即输入结束），就会返回 EOF(End Of File)（EOF 为整型常量 -1）。 在终端(黑框)中手动输入时，系统并不知道什么时候到达了所谓的“文件末尾”，因此需要通过依次按下 Enter 键、Ctrl + z 组合键、Enter 键的方式来告诉系统已经到了 EOF，这样循环才会结束。 边输入边输出问题 Dev-c++默认的编译器编译得到的程序，会采取边输入边输出的交互模式。 类似于下图 尽管这种边输入边输出的交互模式看起来与OJ的先输入再输出的样本样例不同，但在OJ测评机眼中并没有区别，大家无需担心此问题。 D Loong 难度 知识点 1 循环，输入输出 题目分析 题目要求根据输入的正整数 NNN，输出一个级别为 NNN 的 &quot;Loong 字符串&quot;。&quot;Loong 字符串&quot;由一个 L，NNN 个 o，一个 n 和一个 g 组成。这里我们可以利用循环来构建字符串和实现输出。 C 语言中的 for 循环是一种常用的控制流结构，用于重复执行一段代码直到满足特定条件为止。for 循环通常由用分号分割开的三个部分组成： 初始化表达式：在循环开始时执行，并且只会执行一次。通常用于初始化计数器或设置初始条件。 循环条件：在每次迭代开始前被求值，如果结果为真，则执行循环体；如果结果为假，则退出循环。 迭代表达式：在每次循环迭代结束时执行，通常用于更新计数器或改变其他控制变量的值。 示例代码 #include &lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;, &amp;n); // 输入正整数 N printf(&quot;L&quot;); // 打印 L for (int i = 0; i &lt; n; i++) { printf(&quot;o&quot;); // 循环打印 N 个 o } printf(&quot;ng&quot;); // 打印 ng return 0; } E De太想进步了 难度 考点 1 循环 题目分析 ​ 本题主要考察大家对于循环的基本使用以及变量赋值及其意义的理解 ​ 对于第 iii 天，我们需要知道的学习战斗力变化和之前一天有关，即我们需要一个变量 yesterday 来存储前一天的学习战斗力 ​ 在第 i+1i + 1i+1 天中，第 iii 天就成了前一天，即我们将 yesterday 赋值为第 iii 天的值即可通过减法运算得到变化量 示例代码 #include &lt;stdio.h&gt; int yesterday, today, n; int main() { scanf(&quot;%d&quot;, &amp;n); for(int i = 1; i &lt;= n; i++) { scanf(&quot;%d&quot;, &amp;today); if(i != 1) //从第二天开始计算 printf(&quot;%d\\n&quot;, today - yesterday); yesterday = today; } return 0; } F 这里是 BUAA 2024 难度 知识点 2 ascii码 题目分析 不定数量输入，且可能包含空格，用 while(scanf(&quot;%c&quot;, &amp;c)!=EOF) 来实现。 每读入一个字符后进行改动操作，先判断字符 ccc 是否在 B ~ Z 之间的范围内，如果是，就输出 c−1c-1c−1 ，否则输出 ccc。判断和输出都利用 ascii码表中 字母 A ~ Z 对应的整数是连续的的特点。 示例代码 #include &lt;stdio.h&gt; char c; int main() { while (scanf(&quot;%c&quot;, &amp;c) != EOF) { if (c &gt; 'A' &amp;&amp; c &lt;= 'Z') printf(&quot;%c&quot;, c - 1); else printf(&quot;%c&quot;, c); } return 0; } G 橡木蛋糕卷 难度 考点 2 条件语句，循环，多组输入输出 题目分析 将所有的商品价格求和后，与 n−bn-bn−b 的值比较并按要求输出即可。 注：本体可不用使用数组，我们注意到把每件商品的价格加到总和之后，这个价格数据就不需要再利用了。所以我们可以只开一个变量存储商品价值，每次均读将数据取到该变量里即可。 示例代码 #include &lt;stdio.h&gt; int main(){ int n, a, b, sum = 0;//注意总和要初始化 scanf(&quot;%d&quot;, &amp;n); scanf(&quot;%d&quot;, &amp;b); while (scanf(&quot;%d&quot;, &amp;a) != EOF) sum += a; if (n - b &gt;= sum) printf(&quot;Yes\\n%d&quot;, n - sum - b); else printf(&quot;N0\\n%d&quot;, n); return 0; } Author: SiSi H 解方程 难度 考点 3 分支结构 if 的使用 题目分析 由题意分析可知，该方程由于 a,b,ca,b,ca,b,c 取值的变化，可能为二次方程、一次方程，还有可能不含未知量 xxx。 对于二次方程，需要判断判别式的大小，分为两根、一根和无实根的三种情况讨论； 对于一次方程，直接求得一个实根即可； 对于不含未知量的方程，若 c=0c=0c=0，则有无穷多解，否则无解。 示例代码 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main() { int a, b, c, d; double x1, x2, t; scanf(&quot;%d%d%d&quot;, &amp;a, &amp;b, &amp;c); d = b * b - 4 * a * c; if (a != 0) //a不为0，等式为二次方程 { if (d &lt; 0) //判别式小于0，无实数解 printf(&quot;No real root&quot;); else if (d == 0) //判别式等于0，有一个实数解 printf(&quot;%.2f&quot;, -b / 2.0 / a); else //判别式大于0，有两个实数解 { x1 = (-b + sqrt(d)) / 2 / a; x2 = (-b - sqrt(d)) / 2 / a; if (x1 &gt; x2) //保证x1为较小的那个解 { t = x1; x1 = x2; x2 = t; } printf(&quot;%.2f %.2f&quot;, x1, x2); } } else //a为0 { if (b != 0) //a=0, b!=0，为一次方程，有唯一解 printf(&quot;%.2f&quot;, -1.0 * c / b); else if (c != 0) //a=0, b=0, c!=0，无解 printf(&quot;No real root&quot;); else //a=0, b=0, c=0，有无穷多解 printf(&quot;infinite solutions&quot;); } return 0; } I 众里寻它 难度 考点 4 最大公约数 题目分析 本题给定一个平行四边形点阵的侧边和底边点的个数n,mn,mn,m，并询问有多少个点与左下角的原点的连线间没有其它的点阻挡，即能从原点被看见。 就本题所关心的遮挡问题而言，平行四边形的点阵和一个同样n∗mn*mn∗m的矩形点阵并没有区别。如果平行四边形点阵内的第iii行（从下向上计数，从000开始）第jjj个(从左向右计数，从000开始)点能/不能被看见，则矩形点阵中的第iii行第jjj个点也同样能/不能被看见。因此，我们可以在更容易处理的n∗mn*mn∗m的矩形点阵中考虑这一问题。 在矩形点阵中，第iii行第jjj列的点与原点的连线的斜率（以下简称斜率）是ij\\frac{i}{j}ji​。斜率相同的点中，只有离原点最近，即i,ji,ji,j的值最小的点能被看见。这样的点的ij\\frac{i}{j}ji​显然是最简分数。也就是说，能被看见的点应当满足iii与jjj互质，即它们的最大公约数为111的条件。 我们通过二重循环枚举点阵中的每一个点，并用课件P1的第77页上的方法求出这个点的iii和jjj的最大公约数。如果最大公约数为1，我们知道这个点能被看见，并令用于统计答案的ans自增111。另外，为了避免被000除，我们并不在循环中考虑第000行和第000列上的点，而是单独进行判断： 原点始终能被看见。可以令ans的初始值为111来体现这一点。 如果不只有第000行，即n&gt;1n&gt;1n&gt;1的话，则第000列有且仅有第111行上的点能被看见。 如果不只有第000列，即m&gt;1m&gt;1m&gt;1的话，则第000行有且仅有第111列上的点能被看见。 示例代码 1 #include&lt;stdio.h&gt; int main() { int n, m, gcd, ans = 1;//已经把原点统计在内 scanf(&quot;%d%d&quot;, &amp;n, &amp;m);//读入 for (int i = 1; i &lt; n; i++) for (int j = 1; j &lt; m; j++) //枚举除了第0行/列外的每个点 { gcd = i; if (j &lt; gcd) gcd = j; while (!(i % gcd == 0 &amp;&amp; j % gcd == 0)) gcd = gcd - 1; //求出该点i,j的gcd if (gcd == 1) ans = ans + 1; //如果互质，则能被看见 } if (n &gt; 1) ans = ans + 1; //不止一行，加上第1行第0列这个点 if (m &gt; 1) ans = ans + 1; //不止一列，加上第0行第1列这个点 printf(&quot;%d&quot;, ans); return 0; } 如果使用辗转相除法求最大公约数，则可以写出如下效率更高的程序： 示例代码 2 #include&lt;stdio.h&gt; int main() { int n, m, x, y, t, ans = 1; scanf(&quot;%d%d&quot;, &amp;n, &amp;m); for (int i = 1; i &lt; n; i++) for (int j = 1; j &lt; m; j++) { x = i; y = j; while(y != 0) { t = x % y; x = y; y = t; } //x中存储了gcd if (x == 1) ans = ans + 1; } if (n &gt; 1) ans = ans + 1; if (m &gt; 1) ans = ans + 1; printf(&quot;%d&quot;, ans); return 0; } J 36倍数(hard version) 难度 知识点 5 数学，计数 题目分析 题目给出了一个正整数数组 b，要求从中选择两个正整数进行前后拼接，并判断拼接后的结果是否是 36 的倍数。我们需要统计满足条件的有序数对 (i,j)(i, j)(i,j) 的个数。 注意到很重要的一点：要使一个数 xxx 是 363636 的倍数，那么 xxx 一定是 444 和 999 的倍数。而 444 和 999 的倍数都有其特征： 444 的倍数的最后两位可以被 444 整除 999 的倍数的各位数字之和可以被 999 整除 999 的倍数 xxx 的各位数字之和模 999 的结果等于 xxx 对 999 取模的结果 考虑每个数字放在右边，有多少个数字放在它的左边拼接得到的数字是 363636 的倍数。 对于每个 bib_ibi​： bib_ibi​ 不是个位数。bib_ibi​ 是 444 的倍数，那么拼接出来的数一定可以被 444 整除，所有与它拼接后各位数字之和可以被 999 整除的数字都满足要求；bib_ibi​ 不是 444 的倍数，那么拼接出来的数一定不被 444 整除，不符合要求。 bib_ibi​ 是个位数，那么要使拼接后的数是 444 的倍数，首先 bib_ibi​ 一定是偶数。如果 bi∈{0,4,8}b_i \\in \\{0, 4, 8\\}bi​∈{0,4,8}，那么与它拼接的数除了需要满足拼接后各位数字之和可以被 999 整除外，还需满足末位数字是偶数；如果 bi∈{2,6,10}b_i \\in \\{2, 6, 10\\}bi​∈{2,6,10}，那么与它拼接的数除了需要满足拼接后各位数字之和可以被 999 整除外，还需满足末位数字是奇数。 如果 bib_ibi​ 可以被 363636 整除，那么 bib_ibi​ 和它自身拼接也是 363636 的倍数，在统计时会被算进去，因此需要将答案减一。 根据这个思路，我们分奇偶统计出每个数字对 999 取模得到的结果的数量 cnt，然后遍历数组 b 即可。 示例代码 1 #include &lt;stdio.h&gt; #define N 100005 int n, cnt[11]; long long a[N]; int res = 0; int cnt_even[11]; int cnt_odd[11]; int main() { scanf(&quot;%d&quot;, &amp;n); for (int i = 1; i &lt;= n; i++) { scanf(&quot;%lld&quot;, &amp;a[i]); cnt[a[i] % 9]++; if(a[i] % 2 == 0) cnt_even[a[i] % 9]++; else cnt_odd[a[i] % 9]++; } for (int i = 1; i &lt;= n; i++) { if (a[i] &gt; 9 &amp;&amp; a[i] % 4 == 0) { res += cnt[(9 - a[i] % 9) % 9]; } else if (a[i] &lt;= 9 &amp;&amp; a[i] % 2 == 0) { if (a[i] == 0 || a[i] == 4 || a[i] == 8) { res += cnt_even[(9 - a[i] % 9) % 9]; } else { res += cnt_odd[(9 - a[i] % 9) % 9]; } } if (a[i] % 36 == 0) res--; } printf(&quot;%d&quot;, res); return 0; } 示例代码 2 全部采用计数的方式，利用乘法原理计算总数。 #include &lt;stdio.h&gt; int tail[9]; int singletail[9]; int headodd[9]; int headeven[9]; int main(void) { int n; long long b, ans; scanf(&quot;%d&quot;, &amp;n); for(int i = 0; i &lt; n; ++i) { scanf(&quot;%lld&quot;, &amp;b); if (b % 2 == 0) headeven[b % 9]++; else headodd[b % 9]++; if (b &lt; 10 &amp;&amp; b % 2 == 0) singletail[b]++; if (b &gt;= 10 &amp;&amp; b % 4 == 0) tail[b % 9]++; } ans = tail[0] * (headeven[0] + headodd[0]) - tail[0]; for (int i = 1; i &lt; 9; i++) { ans += tail[i] * (headeven[9 - i] + headodd[9 - i]); } ans += singletail[2] * headodd[7] + singletail[6] * headodd[3]; ans += singletail[4] * headeven[5] + singletail[8] * headeven[1]; printf(&quot;%d&quot;, ans); return 0; } - End - ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":" C1 - Solution-23航c","link":"https://github.pansis.site/post/c1-solution-23-hang-c/","stats":{"text":"16 min read","time":943000,"words":3683,"minutes":16},"dateFormat":"2024-03-07"},{"content":"A 愿此行，终抵群星！ c语言程序基本结构 #include &lt;stdio.h&gt; //引用头文件 int main() //main函数 程序的开始 int代表函数返回值为整形 { printf(&quot;Hello，world&quot;); //printf()函数 return 0;//返回值,函数结束位置，main返回值为0代表程序正常结束，否则为异常结束 } 题解 #include &lt;stdio.h&gt; int main() { printf(&quot;May this journey lead us starward.&quot;); return 0; } B 求矩形面积123456 scanf函数 1.库： #include &lt;stdio.h&gt; 2.常见用法：scanf(&quot;格式说明字符串&quot;,&amp;&lt;变量&gt;,&lt;字符数组&gt;.....) scanf(&quot;%d%lld&quot;,&amp;n,&amp;m); ​ &amp;&lt;变量&gt; 代表该变量的地址 格式说明字符串中只能含有%d等说明符，其他的不要加，可能出现意想不到的问题 ​ &lt;字符数组&gt; 不需要加&amp;，本身就代表数组的地址 返回读取变量的个数 3.scanf()常用字段类型说明符 例2-8 %d： － 以十进制方式输入带标记整数 int 十进制 %u： － 以十进制方式输入无符号整数 int 十进制 %x — 以十六进制方式输入整数 int 十六进制 %o： － 以八进制方式输入整数 int 八进制 %f： － 单精度实数； float %lf：－ 双精度实数； double %c： － 输出单个字符； char %s： － 输出字符串。 %llu 以十进制方式输入unsigned long long unsigned long long %lld -以十进制方式输入长整型 long long %hhd 获取signed char 空格 -读取接下来的空格，并作废（用于在读入字符前清除空格）（格式化字符串末尾不能用空格和\\n） 输入 2 3 4 5 scanf(&quot;%d%d%d%d&quot;,&amp;a,&amp;b,&amp;c,&amp;d) //a=2 b=3 c=4 d=5 printf函数 1.库： #include &lt;stdio.h&gt; 2.常见用法： printf(&quot;格式说明字符串&quot;,...,...,...); ....为参数表，与字符串中的%一一对应 3.printf()常用字段类型说明符 例2-8 %d： － 以十进制方式输出带标记整数（正数默认不输出标记）； int 十进制 %u： － 以十进制方式输出无标记整数； unsigned int 十进制 %x — 以十六进制方式输出整数 int 十六进制 %o： － 以八进制方式输出整数 int 八进制 %f： － 以小数方式输出单、双精度实数； double 小数方式 %e： － 以指数方式输出单、双精度实数； double 科学计数法 %g： － 以%f，%e中较短的输出宽度输出单、双精度实数； double %c： － 输出单个字符； char %s： － 输出字符串。 char* //%s对应的参数实质上是字符串第一个字符对应的指针，当printf输出到'\\0'时停止输出该字符串 %lld -以十进制方式输入长整型 long long %llu 以十进制方式输出unsigned long long unsigned long long 4.完整的字段说明序列 %+d %[flags] [width] [.precision] type flags 格式标志 width字段宽度 .precision精度 type类型(必填) 5.flags 格式标志 标志 意义 缺省 - 结果左对齐，右边填空格 结果右对齐 + 在有符号的数据前输出标记(正号或负号) 只对负数加符号 0 数据在字段宽度内加前导0 不加前导0 # 对八进制和十六进制数（o,x,X）显现前缀O,Ox 不加前缀 6.width字段宽度 在%与字母之间 插入 整数 代表该字段的最小宽度 7.precision精度 在%与字母之间 插入 小数 代表要保留的小数位数 %.1f //输出保留一位小数 %.2f //输出保留二位小数 (位数不足以零补位) 题解 #include &lt;stdio.h&gt; int main() { double xa, ya, xb, yb, area; scanf(&quot;%lf %lf %lf %lf&quot;, &amp;xa, &amp;ya, &amp;xb, &amp;yb); area=(xa-xb)*(ya-yb); printf(&quot;%.2lf&quot;, area); } #include &lt;stdio.h&gt; int main() { double xa, ya, xb, yb; scanf(&quot;%lf %lf %lf %lf&quot;, &amp;xa, &amp;ya, &amp;xb, &amp;yb); printf(&quot;%.2lf&quot;, (xa-xb)*(ya-yb)); } C 整除div 不定组输入 scanf返回值说明 scanf()函数返回成功赋值的数据项数，读到文件末尾而没有数据时返回 EOF(-1)。 输入 2 3 4 5 e=scanf(&quot;%d%d%d%d&quot;,&amp;a,&amp;b,&amp;c,&amp;d) //a=2 b=3 c=4 d=5 //e=4 输入 2 3 4&lt;文件尾&gt; e=scanf(&quot;%d%d%d&quot;,&amp;a,&amp;b,&amp;c) //a=2 b=3 c=4 d=5 //e=3 e=scanf(&quot;%d%d%d&quot;,&amp;a,&amp;b,&amp;c) //e=-1 (EOF) 不定组输入 while (scanf(&quot;%d%d&quot;, &amp;a, &amp;b) != EOF) { //读取了两个int整数，分别存入变量a，b中 //下面填写相应的处理代码 } 整除问题 在C语言中，如果变量 a、b 都是整数类型（int、long long等），则a/b得到的结果就是 a 整除 b。 int a = 9, b = 4; int c = a / b; //赋值后的c=2，而不是2.25 但如果 / 两侧有一个或两个 double 类型，则a/b得到的结果就是a除以b。 double a = 9; int b = 4; double c = a / b; //赋值后的c=2.25 题解 #include &lt;stdio.h&gt; int main() { int a,b; while ((scanf(&quot;%d%d&quot;,&amp;a,&amp;b))!=EOF) { if (b==0) printf(&quot;Cann0t divide by 0\\n&quot;); else printf(&quot;%d\\n&quot;,a/b); } } 常见错误 忘记\\n，导致PE ys 0打成o D Loong putchar() char a='1'; putchar(a); scanf(&quot;%c&quot;,a); 题解 #include &lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;,&amp;n); putchar('L'); while (n--) { putchar('o'); } printf(&quot;ng&quot;); } E De太想进步了！ #include &lt;stdio.h&gt; int main() { int n; scanf(&quot;%d&quot;,&amp;n); int old; scanf(&quot;%d&quot;,&amp;old); for (int i = 1; i &lt; n; ++i) { int now; scanf(&quot;%d&quot;,&amp;now); printf(&quot;%d\\n&quot;,now-old); old=now; } } F 这里是 BUAA 2024 字符本质就是数字 #include &lt;stdio.h&gt; int main() { char a='a'; printf(&quot;%d %c\\n&quot;,a,a);//字符a的ACill码为97 char aa=97; printf(&quot;%d %c&quot;,aa,aa); } 'a' 等价于 数字97 字符间的计算 大小写转换 #include &lt;stdio.h&gt; int main() { //小写转大写 char a='a'; printf(&quot;%c&quot;,a-32); } #include &lt;stdio.h&gt; int main() { //小写转大写 char a='a'; printf(&quot;%c&quot;,a-'a'+'A'); } 题解 #include &lt;stdio.h&gt; int main() { char c; while ((c=getchar())!=EOF) { if (c&gt;='B'&amp;&amp;c&lt;='Z') c--; printf(&quot;%c&quot;,c); } } #include&lt;stdio.h&gt; int main() { char c; while(scanf(&quot;%c&quot;, &amp;c) != EOF) { if(c &gt;= 'B' &amp;&amp; c &lt;= 'Z') { c--; printf(&quot;%c&quot;,c); } return 0; } getchar char aa=getchar(); //等价 char aa; scanf(&quot;%c&quot;,&amp;aa); getchar的不定组输入 xxxx 已学的输入输出函数 //通用型 scanf(&quot;xx&quot;,xxx); printf(&quot;xx&quot;,xxx); //用于字符 a=getchar(); putchar(a); //用于字符串 //暂时未学 G 橡木蛋糕卷 #include &lt;stdio.h&gt; int main() { int n;int a,b; scanf(&quot;%d%d&quot;,&amp;n,&amp;b); while ((scanf(&quot;%d&quot;,&amp;a))!=EOF) { n-=a; } if (n&gt;=b) { printf(&quot;Yes\\n%d&quot;,n-b); } else { printf(&quot;N0\\n%d&quot;,n); } } 题解 #include &lt;stdio.h&gt; #include &lt;math.h&gt; int main() { int a,b,c; scanf(&quot;%d%d%d&quot;,&amp;a,&amp;b,&amp;c); if (a==0) { if (b==0) { if (c==0) printf(&quot;infinite solutions&quot;); else printf(&quot;No real root&quot;); } else printf(&quot;%.2f&quot;,-c/(1.0*b)); return 0; } double delta=b*b-4*a*c; if (delta&lt;0) printf(&quot;No real root&quot;); else if (delta==0) printf(&quot;%.2f&quot;,-b/(2.0*a)); else { double x1=(-b+sqrt(delta))/(2.0*a); double x2=(-b-sqrt(delta))/(2.0*a); if (x1&gt;x2) printf(&quot;%.2f %.2f&quot;,x2,x1); else printf(&quot;%.2f %.2f&quot;,x1,x2); } } ","tags":[{"index":8,"name":"23航C","slug":"24hc","used":true,"link":"https://github.pansis.site/tag/24hc/"}],"title":"C1讲解-23航c","link":"https://github.pansis.site/post/c1-jiang-jie-23-hang-c/","stats":{"text":"9 min read","time":521000,"words":1839,"minutes":9},"dateFormat":"2024-03-07"},{"content":"一、计算机相关概念 1、计算机典型硬件组成 1、组成：CPU、主存储器、输入输出设备 2、CPU CPU可以抽象为2大部分：数据通路、控制器 数据通路：执行指令所需的计算与存储的功能部件集合 PC：类似于C语言的指针，指向要执行的指令 ALU：完成各类计算，如加减乘除 寄存器堆：临时性存储参与运算的数据以及运算结果 控制器：根据指令控制数据通路的各个部件完成相应操作 例如加法指令，就需要控制ALU完成加法运算 3、主存储器：又称内存 2、软硬件层次结构 1、一个完整的计算机系统，可以划分为硬件和软件两大部分 2、硬件和软件者之间的交互界面就是指令集 ","tags":[{"index":-1,"name":"计算机组成原理","slug":"jsjzcyl","used":true,"link":"https://github.pansis.site/tag/jsjzcyl/"}],"title":"0.1 绪论","link":"https://github.pansis.site/post/0.1 绪论/","stats":{"text":"1 min read","time":48000,"words":228,"minutes":1},"dateFormat":"2024-03-05"},{"content":"一、搜索方法 1、搜索问题的表示方法 状态空间表示法、与或树表示法 2、状态空间表示法 1、状态：描述问题任意时刻状况的数据结构 2、算符：使问题从一个状态转移到另一个状态的操作 3、例子 3、与或树表示法 用“关系”和“节点”来表示问题 1、或结点 2、与结点 3、与或树表示 S0=A+D=BC+EFS_0=A+D=BC+EFS0​=A+D=BC+EF 4、搜索方法 盲目搜索、启发式搜索 5、盲目搜索 1、种类：枚举搜索、深度优先搜索、广度优先搜索 6、启发式搜索 1、类型：A*算法、蒙特卡洛树搜索 二、枚举搜索 1、核心思想 枚举所有的可能 2、前提条件 可预知候选答案的数量 候选答案的范围在求解之前必须有一个确定的集合 3、枚举方法 循环枚举、子集枚举、排列枚举 4、优点 实现方法简单、结果最优 5、缺点 速度慢 6、举例 三、深度优先搜索 1、遍历规则 1）如果和 当前结点 相邻的结点已经访问过，则不能再访问； 2）每次从和 当前结点 相邻的结点中寻找一个编号最小的没有访问的结点进行访问； 2、例子 四、广度优先搜索 1、基本流程 从图中的某一顶点V0V_0V0​开始，先访问V0V_0V0​； 访问所有与V0相邻接的顶点V1，V2，......，VtV_1，V_2，......，V_tV1​，V2​，......，Vt​； 依次访问与V1，V2，......，VtV_1，V_2，......，V_tV1​，V2​，......，Vt​相邻接的所有未曾访问过的顶点； 循此以往，直至所有的顶点都被访问过为止。 2、举例 3、寻找最短路径（BFS） 从A到H，找出一条经过城市最少的一条路线。 采用BFS方法遍历城市图，同时额外记录每个城市的上个城市。 五、A*算法 1、地图栅格化 •把要搜寻的区域划分成了正方形的格子 每个格子的状态就是可走 (walkable) 和不可走 (unwalkable) •方格的中心点称为节点 (nodes) 。 为什么不直接描述为方格呢？因为我们有可能把搜索区域划为为其他多边形而不是正方形，而节点可以放在任意多边形里面，可以放在多变形的中心，也可以放在多边形的边上。使用节点描述因为它最简单。 2、地图情景 假设某人要从 A 点移动到 B 点，但是这两点之间被一堵墙隔开。绿色是 A ，红色是 B ，中间蓝色是墙。 3、移动代价 4、算法流程 详见A星算法详解(个人认为最详细,最通俗易懂的一个版本) - 知乎 (zhihu.com) 1、开始搜索 从起点 A 开始，把它就加入到一个openlist（开放列表）中。 查看与起点 A 相邻的方格，把其中可走的 (walkable) 或可到达的 (reachable) 方格也加入到openlist中。 把起点 A 设置为这些方格的父亲 (parent node) 。 把 A 从 openlist 中移除，加入到 closelist( 封闭列表 ) 中。 2、继续搜索 此时找到F=40的方格 • 从 openlist 中选择 F 值最小的 ( 方格 ) 节点，放到 closelist 中，F=40 • 检查所有与它相邻的方格，忽略closelist 中或是不可走 (unwalkable) 的方格，对于某个相邻方格 ​ •若其不在openlist，把它加入到openlist。并把我们选定的方格设置为这些新加入的方格的父亲。 ​ •若在openlist，检查该路径是否更优。如果按照原本的路径（从父节点过来）的G1是否大于经由当前方格 G2 。如果G2小，则说明后者更优，更换该相邻方格的父亲，并重新计算G=G2. · 检查结束后，返回第一步，重新选择F最小的方格。（此时找到F=54的方格） 3、同理上述循环 六、蒙特卡洛树搜索 1、组合博弈问题 特点： 零和：一方的收益必然意味着另一方的损失，收益和损失相加总和永远为零，故双方不存在合作的可能 信息对称：游戏的所有信息、状态、规则都是所有玩家已知的； 确定性：游戏终将结束，且有特定的判定规则判断输赢； 离散：博弈状态是有限的集合（如棋盘大小和落子位置）； 序列：博弈过程是序列进行的，双方轮流操作。 2、博弈树 1、用来表征一个博弈 2、顶部为树的根节点，表示井字棋博弈的初始状态，即空白棋盘 3、任何从一个节点向另一个节点的转换被称为一个 action ； 4、从一个根节点到一个端节点的树遍历表征了单个博弈过程。 2、蒙特卡洛树中的节点 完全未展开节点、展开节点、完全展开节点、 完全未展开节点：其子节点都没有被访问 展开节点：其部分节点被访问 完全展开节点：其子节点都被访问 还有种节点，其对应的局面代表游戏结束。 3、蒙特卡洛树评估函数UCT 1、公式： ​ 2、探索率存在的意义 不能每次都只选择“最有利的/最不利的”，因为这会意味着搜索树的广度不够，容易忽略实际更好的选择 4、算法流程 节点选择 →\\to→ 拓展 →\\to→ 模拟 →\\to→ 回溯 →...\\to ...→... →\\to→ 终止 1、选择 从根节点往下走，每次都选一个“最值得看的子节点”（UCT最大的节点），直到来到一个完全未展开或展开节点（指这个局面存在未走过的后续走法），如图中的 3/3 节点。 2、拓展 给这个节点加上一个 0/0 子节点，对应之前所说的“未扩展的子节点”，就是还没有试过的一个着法。 3、模拟 从没有试过的走法开始，用快速走子策略走到底，得到一个胜负结果。 4、回溯 把模拟的结果加到它的所有父节点上。例如第三步模拟的结果是 0/1（代表黑棋失败），那么就把这个节点的所有父节点加上 0/1。 5、终止 当选择过程中，一段时间内不会出现新的节点时，搜索算法收敛终止。 5、快速走棋策略 快速走子策略适合选择一个棋力很弱但走子很快的策略。因为如果这个策略走得慢，虽然棋力会更强，结果会更准确，但由于耗时多了，在单位时间内的模拟次数就少了，所以不一定会棋力更强，有可能会更弱。这也是为什么我们一般只模拟一次，因为如果模拟多次，虽然更准确，但更慢。 ","tags":[{"index":-1,"name":"人工智能","slug":"rgzn","used":true,"link":"https://github.pansis.site/tag/rgzn/"}],"title":"2.1 搜索算法","link":"https://github.pansis.site/post/2.1 搜索算法/","stats":{"text":"7 min read","time":378000,"words":1768,"minutes":7},"dateFormat":"2023-09-26"},{"content":"一、制导 1、制导方式 全程制导、中制导、末制导 2、制导分类 惯性制导、卫星制导、激光制导、红外制导、雷达制导 二、思考题 1、CEP 圆形公算误差是用于测量武器系统准确度的尺度。 以瞄准点为中心，包含50%弹着点的圆的半径就叫做该导弹的元概率误差CEP(50%) 。 CEP(95%) 这个半径越小，导弹的命中精度越高。 2、地球大气层是如何分层的？航空和航天的界限是什么？ 大气层根据垂直方向上气温、大气扰动程度、电离现象等特征，从低到高分为对流层、平流层、同温层、热电离层、散逸层。 航空和航天的界限是100km的卡门线为界。 航空：地面以上，100千米以下的大气层内。 航天：100千米以上，太阳系以内。 3、航空器与航天器最显著的区别是什么？ 活动区域不同，航空器是在大气层中飞行,高度在100km以下。 航天器运行轨道的近地点高度至少在100km以上。 4、航天器的三大系统是哪些，作用分别是什么？ 航天器平台的最小系统组成部分 结构/机构分系统（也称为总体）、推进分系统、GNC分系统。 结构/机构分系统通常又称为航天器总体，是航天器的骨架； 推进分系统为航天器提供飞行动力； GNC分系统是航天器按照设计航迹精确稳定飞行的关键。 5、制导、导航与控制之间的关系是什么？ 制导是按照一定准则确定飞行器从一个地点飞行至目的地的最优航迹。 导航是通过某种测量方式实时获取飞行器的位置、速度、姿态角、角速度、加速度等导航信息。 控制是基于实时导航测量的飞行状态与事先制导规划的飞行状态之间的偏差，按照设计的控制算法，确定控制量，驱动执行机构，产生控制力矩，改变飞行器的飞行状态，以趋于制导规划的飞行状态。 制导完成航迹规划，导航实现航天器飞行状态的实时测量，控制是基于导航信息，调整控制航天器的飞行状态，以完成制导规划的航迹，制导、导航与控制三者是配合工作的，是有机整体。 6、请举出三种或以上的导航方法 惯性导航、天文导航、卫星导航、视觉导航、地形辅助导航。 7、请举出三种或以上的组合导航算法 最小二乘算法、维纳滤波器、Kalman滤波算法、非线性滤波算法、联邦滤波算法。 ","tags":[{"index":-1,"name":"组合导航原理","slug":"zhdhyl","used":true,"link":"https://github.pansis.site/tag/zhdhyl/"}],"title":"1.1 组合导航概论","link":"https://github.pansis.site/post/1.1 组合导航概论/","stats":{"text":"3 min read","time":149000,"words":728,"minutes":3},"dateFormat":"2023-09-12"},{"content":"信息与编码 一、信息量的度量 1、定义： 面临N中同等可能的选择，给定一个信息，能将范围缩小至M，则该信息提供的信息量为log2(N/M)log_2(N/M)log2​(N/M) 2、公式： log2(N/M)log_2(N/M)log2​(N/M) 3、例子： 投掷两枚筛子的结果 log2(36/1)=5.2bitslog_2(36/1)=5.2bitslog2​(36/1)=5.2bits 二、编码 1、定义： 编码是对信息进行表征的过程 2、数制： 表示数量的规则（对数进行编码）（二进制、十进制等） 3、码制： 表示事物的规则（对事物进行编码）（对符号、文字的编码） 三、数制 **1、**进位计数制是最常见的数制。 **2、R进制：**逢R进一成为R进制。 ​ 进位基数 ：R，即每个数位 可以出现的数码个数 3、R进制数值大小公式 四、进制之间的相互转换 1、R进制转十进制 直接使用R进制数值大小公式即可 2、十进制转二进制 整数的转换:整数连除，取余逆序 纯小数的转换：小数连乘，取整顺序 十进制转其他进制类似于十进制转二进制。 3、二进制互转八进制 二进制转八进制： 将(10011010.111101)2(10011010.111101)_2(10011010.111101)2​转 换为八进制数 八进制转二进制： 将(316.54)8(316.54)_8(316.54)8​转换为二进制数 二进制互转十六进制与此类似。 4、八进制互转十六进制 以二进制位中介进行转换 五、二进制数的运算 1、原码补码反码 原码： 符号位在前，0表示正数，1表示负数，数值位跟随符号位后面。 反码： 正数的反码为其本身 ​ 负数的反码通过其数值位逐位取反得到 补码： 正数的补码为其本身 ​ 负数的补码是其绝对值的原码取反加一。 2、运算实例 用补码表示有符号数，则可以用加法运算来实现减法运算， 同时不影响运算的正确性。 加法/减法：（补码直接计算，如果多出来一位，该位舍弃） 乘法: 除法: 六、码制 1、BCD码 定义：用二进制码元来表示十进制数的代码，也 称为十进制码 BCD码不是二进制数 2、ASCII码 略 ","tags":[{"index":-1,"name":"vlsi","slug":"vlsi","used":true,"link":"https://github.pansis.site/tag/vlsi/"}],"title":"1.1信息与编码","link":"https://github.pansis.site/post/1.1信息与编码/","stats":{"text":"3 min read","time":134000,"words":604,"minutes":3},"dateFormat":"2023-09-11"},{"content":"一、导航 1、定义 引导运载体从出发点到达目的地的整个过程，包含导航、制导与控制三个过程。 2、导航系统 定义： 为了完成载体引导任务的设备称为导航系统。 引导载体航行所需的基本参数： 即时位置、速度、航向 （均由导航系统提供） 3、制导系统 定义： 按选定的规律对载体进行引导，调整其运动轨迹直至以允许的误差击中目标或到达目的地。 功能： 测量载体与目标的相对位置 计算出实际飞行轨迹和理论轨迹的偏差 给出消除偏差的指令 二、导航系统的分类 1、分类 自主导航： 惯性导航、天文导航、物理场导航 非自主导航（无线电导航）： 卫星导航 2、惯性导航 见下 3、卫星导航 优点： 全天候、高精度、误差不积累 缺点： 抗干扰性能较差 4、天文导航 优点： 完全自主，误差不积累 缺点： 导航精度受大气条件影响较大 5、物理场导航 优点： 误差不积累、具有良好的隐蔽性和抗干扰性、提供绝对位置信息 缺点： 需存储大量地球物理场数据、导航信息不连续 三、无线电导航 1、无线电导航的基本原理 运载体上的导航设备通过接收地面台或空中卫星上的导航信号，根据电磁波的传播特性，测出信号传播时间、相位、频率与幅度后，即可测出运载体相对于号航台的方位、距离、距离的变化率等几何参数，建立起运载体与导航台的相对位置关系，从而获得运载体当前的位置。 2、电磁波传播的基本特性 (1) 电磁波在在均匀理想媒质中，沿直线传播 (2) 电磁波在自由空间的传播速度是恒定的 ; (3) 电磁波在传播路线上遇到障碍物或在不连续介质的界面上时会发生反射 。 3、无线电导航优缺点 优点： 不受时间、天气限制。精度高，作用距离远，定位时间短，设备简单可靠 缺点： 易被发现和干扰，需要载体外的导航台支持。 四、惯性导航 1、定义 利用惯性元件 (陀螺仪、加速度计) 测得的加速度和角速度经过积分和运算得到运动体的位置、速度和姿态等导航信息。 2、优点 完全自主，不依赖任何外界信息，只依靠陀螺仪与加速度计 不受外界干扰，隐蔽性好，使用环境不受限制（海陆空、水下） 运动参数完备，提供位置、速度、加速度、姿态信息 实时性好，数据更新频率高 短期精度、稳定性好 3、缺点 误差随时间积累，需要其他导航方法进行定期校正 4、分类 按有无机电式实体平台： 平台式、捷联式 按选用的陀螺仪类型不同： 液浮陀螺、三浮陀螺、挠性陀螺、静电陀螺、激光陀螺慢导系统和光纤陀螺惯导系统等 按装备对象不同： 舰载、机载和车载惯导系统等。 5、特点 自主性：无需任何导航台站，导航功能完全可由惯性导航系统自身来完成。 隐蔽性：不向载体以外发出任何信号，敌方无法搜索或发现它的工作信息。 抗干扰：不受外部电磁环境影响，敌方也无法实施电磁干扰和控制。 全球性：不受地域限制，具有全球导航能力。 连续性：能够连续、实时提供导航信息。 完备性：既能提供载体的位置信息又能提供载体的姿态、速度和时间信息 6、影响惯性系统精度和稳定性的主要因素 陀螺仪和加速度计的性能 7、惯性导航系统的两大关键传感器 陀螺仪和加速度计 加速度计：精度较高，对系统的误差影响较小 陀螺仪：结构复杂、制造困难且其漂移误差对惯导系统精度影响很大（技术瓶颈） 陀螺仪: 机械陀螺、光学陀螺、MEMS陀螺及 加速度计: 挠性加速度计、摆式加速度计、XXXXXX 6、平台式与捷联式惯导系统的特点 平台式惯导系统 惯性平台能够隔离载体的角振动，为惯性仪表提供了较好的工作条件; 平台能够直接建立导航坐标系，计算方便，容易补偿和修正测量仪表的输出; 结构复杂，尺寸过大，价格昂贵。 捷联式惯导系统 省去了物理平台，结构简单，体积小，维护方便; 陀螺仪和加速度机直接安装在载体上，工作条件不好,从而影响仪表的测量精度。 捷联惯导可靠性高，初始对准快，应用更广泛。 ","tags":[{"index":-1,"name":"惯性导航基础","slug":"gxdhjc","used":true,"link":"https://github.pansis.site/tag/gxdhjc/"}],"title":"1.1惯性导航概论","link":"https://github.pansis.site/post/1.1惯性导航概论/","stats":{"text":"5 min read","time":266000,"words":1312,"minutes":5},"dateFormat":"2023-09-11"},{"content":"Pycharm是一款强大的Python IDE，提供了许多实用的功能，其中之一就是代码提示。代码提示是一种自动补全代码、提示代码错误、提示当前代码风险的功能，它可以大大提高编写代码的效率和减少出错的概率。 代码补全 开启代码补全功能以后，PyCharm将会根据我们正在输入的代码来智能提示我们需要的关键字、变量名、函数名等内容。 比如在Python中，如果要打印输出信息，我们需要使用print()函数。在使用PyCharm时，只需要输入&quot;pri&quot;，IDE将直接提供print函数候选项，此时我们只需要按下回车或Tab键就可以补全第一个候选项。如果选择其他候选项，按方向键选择即可。 错误提示 在编写代码时，由于疏忽等原因，我们所写的代码无法正常运行。而Pycharm可以在我们运行之前，就能为我们提示代码存在错误。 下面列出Python中常见的语法错误： 1、代码不对齐 Python与C语言以及其他大多数语言一个巨大区别是Python采用缩进的方式构成代码块，而其他语言采用{}和;的方式来构成代码块。这就要求Python有严格的代码对齐，以确保解释器能够正常识别代码。 如果在使用C语言编写代码时严格遵循代码缩进，那么在Python语言中你将很少出现代码不对齐的情况。 Python的代码对齐可以用C语言的语法进行理解。 在C语言中，{}中的代码组成一个代码块，而Python中两行代码属于同一代码块的标志是两者具有相同的缩进，所以Python代码块中的代码是对齐的。 在C语言中，代码块中每一条代码用;隔开（回车仅仅是为了代码可读性，对于实际代码没有作用），而Python使用回车隔开。 Pycharm可以帮助识别Python代码中存在的代码不对齐问题。 2、调用不存在的函数或函数名报错 注意这种情况下，Pycharm提供的更正提示并不正确。 3、未引入库（下图未引入opencv库） 这种情况，可以点击蓝色文字，IDE将自动安装库（但无法安装opencv，具体原因可自己上网深入了解）（同时该提示自动安装库仅适用于Virtualenv环境下的Python，不适用于Conda环境下的Python），并在代码中自动导入该库。 代码警告 当Pycharm出现代码警告时，你的代码能够正常运行，但代码中可能存在冗余甚至不符合代码逻辑的问题。 1、部分变量赋值无意义 下图abc()函数中，a作为局部变量，在该函数中没有任何实际作用，此时提示该赋值无效。 上述代码仅仅是一个简单的实例，我们能一眼看出该赋值无效。但当代码变得十分复杂时，这种提示有时能快速帮我们排除代码问题。 2、无效条件 下面是一个简单的无效条件实例。该if函数对True进行判定，结果一定为真，无论其他代码怎么样，在这个if语句下，一定会执行print(&quot;123&quot;)，一定不会执行print(&quot;456&quot;)，此时IDE提示我们else下的语句无法被访问。 代码运行中常见错误 与C语言类似，在代码运行中，如果运行出现错误，代码往往会提供错误信息，包括错误代码运行位置和错误原因。 1、变量未初始化定义就调用 引用 pycharm代码提示 - 优草派 (ycpai.cn) ","tags":[{"index":-1,"name":"Python与OpenCV","slug":"L7r9STb75","used":true,"link":"https://github.pansis.site/tag/L7r9STb75/"}],"title":"Pycharm代码提示","link":"https://github.pansis.site/post/pycharm-dai-ma-ti-shi/","stats":{"text":"4 min read","time":206000,"words":984,"minutes":4},"dateFormat":"2023-09-05"},{"content":"Pycharm调试 **对于编程学习，可以说调试能力是最重要的技能，没有之一！**所谓调试程序，就是控制编译器一行一行地执行代码，过程中可以清楚看到每个变量值的变化情况、函数的调用过程等，进而发现程序中隐藏的错误或者低效的代码。 添加断点 *添加断点是调试功能的核心* 左键添加断点，主要是在比较容易出错，或者有怀疑点的位置。 当代码运行到此处时，IDE将暂停程序，此时查看当前程序运行状态，包含变量值、线程等信息。 简单调试实例 以下边的代码为例子: n=int(input()) cnt=0 for j in range(n): cnt=cnt+1 print(cnt) 点击调试按钮，程序将会开始运行。 根据上述代码要求，我们先在控制台输入 555 并回车，将 nnn 赋值为 555 。 回车后断点行会变为蓝色，意味着此时代码运行到第 444 行并停止 下方控制台显示变量 nnn 由于执存储了键盘的输入变为了 555 ， cntcntcnt 由于还没有执行仍为 000 点击上图标记按钮，代码会继续执行，直至再次遇到断点，根据逻辑cnt由于执行了一次变为了1，而n由于没有操作所以还是5 调试实战 以OpenCV的一个简单实例，展示调试过程中查看变量的作用以及如何暴露代码逻辑错误问题。 下面这段代码使用OpenCV读取一组图片，利用现有函数检测图片中的圆，如果图片中有圆则显示，否则不显示。 在D:\\下载\\test文件夹下存有两张图片用于检测圆。 import cv2# 调用opencv库 import os foldpath='D:\\下载\\test' filename=os.listdir(foldpath)#读取foldpath文件夹下的文件名 for i in filename: img=cv2.imread(foldpath+'\\\\'+i)#使用opencv读取图片 #转为灰度图 img1=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #检测图片中的圆,参数分别为图片，检测方法，分辨率，最小距离， # param1(param2)为边缘检测时使用的梯度值上下限，minRadius为最小半径，maxRadius为最大半径 circles = cv2.HoughCircles(img1, cv2.HOUGH_GRADIENT, 1, 100, param1=100, param2=30, minRadius=300, maxRadius=0) #如果检测到圆，标记出圆的位置并显示 if circles is not None: for circle in circles[0]: #坐标行列 x=int(circle[0]) y=int(circle[1]) #半径 r=int(circle[2]) #在原图用指定颜色标记出圆的位置 img=cv2.circle(img,(x,y),r,(0,0,255),-1) #显示新图像 cv2.imshow('res',img) cv2.waitKey(0)#等待按键按下 cv2.destroyAllWindows()#清除所有窗口 我们运行上述代码，会出现下面的报错信息 报错信息十分清楚的告诉我们路径名称出现问题。尽管我们文件夹路径写的是D:\\下载\\test，但Python中存在转义字符，\\应当写成\\\\。 我们将foldpath改为D:\\\\下载\\\\test，继续运行代码。 报错代码在转灰度图的函数上，那么我们将断点放在该函数上进行调试，观察代码运行情况。 当代码运行到该函数时程序暂停，在调试界面我们会发现img变量竟然为None，也就是说img=cv2.imread(foldpath+'\\\\'+i)代码根本就没有读取照片。 其实这个是新手在使用OpenCV时常常会遇到的问题。OpenCV不支持中文路径，但foldpath中出现了中文，导致图片读取失败。 针对这个问题，我们将图片放置在D:\\123\\test路径下并修改foldpath变量，重新运行代码。 此时代码不再报错，但发现并没有弹出图片，但在D:\\123\\test下我们的的确确放了一张有圆的图片和没有圆的图片。 将断点放在cv2.imshow('res',img)上，调试代码。发现代码根本没有暂停，也就意味着代码中if circles is not None:条件始终为假，cv2.HoughCircles函数没有检测到圆。 下面我们就要重新审视cv2.HoughCircles函数中设置的参数，我们发现minRadius被设置为300，可能最小半径设置过大导致将图片中的圆忽略。所以我们将该参数设置为0，重新运行代码。 此时，成功弹出弹窗。在第一张图片中成功识别出圆。 下面是调试后成功运行的代码 import cv2# 调用opencv库 import os foldpath='D:\\\\123\\\\test' filename=os.listdir(foldpath)#读取foldpath文件夹下的文件名 for i in filename: img=cv2.imread(foldpath+'\\\\'+i)#使用opencv读取图片 #转为灰度图 img1=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #检测图片中的圆,参数分别为图片，检测方法，分辨率，最小距离， # param1(param2)为边缘检测时使用的梯度值上下限，minRadius为最小半径，maxRadius为最大半径 circles = cv2.HoughCircles(img1, cv2.HOUGH_GRADIENT, 1, 100, param1=100, param2=30, minRadius=0, maxRadius=0) #如果检测到圆，标记出圆的位置并显示 if circles is not None: for circle in circles[0]: #坐标行列 x=int(circle[0]) y=int(circle[1]) #半径 r=int(circle[2]) #在原图用指定颜色标记出圆的位置 img=cv2.circle(img,(x,y),r,(0,0,255),-1) #显示新图像 cv2.imshow('res',img) cv2.waitKey(0)#等待按键按下 cv2.destroyAllWindows()#清除所有窗口 cv2.destroyAllWindows()#清除所有窗口 调试功能介绍 上面两个实例初步介绍了断点、断点暂停、变量查看功能，掌握上述功能就基本上可以胜任绝大多数的程序调试。下面系统地介绍Pycharm调试功能和进阶功能。 转载改编于PyCharm代码调试_pycharm调试_TEST7780的博客-CSDN博客 调试界面 变量查看窗口 程序运行时的变量和值都会显示在此窗口，每执行一行代码，变量可能会改变。 调试控制窗口 通过调试控制窗口，来控制代码运行路径。 线程控制窗口 如果当前程序为多线程，可通过线程控制窗口下的下拉框来切换线程。 程序控制窗口 通过程序控制窗口，来控制程序运行终止。 程序控制功能 ","tags":[{"index":-1,"name":"Python与OpenCV","slug":"L7r9STb75","used":true,"link":"https://github.pansis.site/tag/L7r9STb75/"}],"title":"Pycharm调试","link":"https://github.pansis.site/post/pycharm-diao-shi/","stats":{"text":"6 min read","time":358000,"words":1572,"minutes":6},"dateFormat":"2023-09-04"},{"content":"OJ网址：https://accoding.buaa.edu.cn/ 登录 进入比赛之前，需要先登录才能看到课程比赛。 账号密码请参照第一节课PPT 用户名：个人8位学号@buaa.edu.cn 默认密码：六个0 加入课程 如果在比赛列表中没有2024级-航空航天大类-C语言程序设计的比赛，请先加入课程小组。（一般开赛前5分钟会出现，耐心等待）（比如第一次上机比赛 C1 - 2024级C语言程序设计第一次上机赛）。 等待管理员同意后即可加入课程小组。 比赛与代码提交 登录OJ账号后，在赛事中选择参加的上机赛或者练习赛。（注意：第一次上机比赛名称为C1 - 2024级C语言程序设计第一次上机赛，红框比赛只是作为示例） 进入比赛，在题目一栏页面里。中间是该比赛的题目，包含完整的题目描述、输入输出、样例和数据范围。右侧是该题目的提交记录和评测结果。 提交代码时，点击上图右上角的提交按钮。 在弹出的代码框中，选择语言c，并将在本地编写的代码复制到下方的代码区。然后点击提交。OJ系统将会对你提交的代码进行评测。 点击ID列的单元格，可以查看自己所提交的代码。 评测反馈 在比赛页面中，选择左侧“我的提交”页面可以查看每道题的每个测试点的具体反馈。评测系统会根据你的代码通过情况给予不同的结果反馈。 每个反馈结果的含义如下： AC（Accepted）：已通过。 你的程序通过了所有测试数据，获得分数。 需要注意的是，Accepted ≠ Correct。即：你的程序只是通过了命题人给出的所有数据，依然有存在潜在错误的可能性。 CE（Compile Error）：编译错误。 你的程序存在语法错误（C / C++ 最常见的是缺少分号、缺少括号、使用了中文标点符号或者函数调用错误等等）或者OJ系统不支持的写法（较少见）。 此时应当仔细检查代码在本机能否通过编译，改正后再次提交。 PE（Presentation Error）：输出格式错误。 你的程序几乎能AC了，但是和标准输出数据有点细微的差距（大小写，空格数量，换行数量之类的）。 此时应当仔细观察题目给出的输出样例，确认格式无误（选中数据粘贴到编辑器最为稳妥）。 WA（Wrong Answer）：答案错误。 程序输出的结果有错误，与期望输出不匹配（也有可能是因为缺少了必要的换行和空格）。 请检查程序是否出现了致命的逻辑错误。 TLE（Time Limit Exceed）：超出运行时间限制。 你的程序可能因为时间效率不高或者出现了死循环，所以未能在规定的时限内运行结束。 MLE（Memory Limit Exceed）：超出运行内存限制。 你的程序占用的内存超过了规定值，可能是因为使用了过大的数组，也可能是没有做到内存释放（较少见）。 REG/REP（Runtime Error （***））：运行时错误。 这种错误分很多种，需要具体问题具体分析。最常见的是 SIGSEGV，一般是因为访问了不存在的数组元素或者函数调用过多导致爆栈。 OE（Other Error）：其他错误，同样需要具体问题具体分析。 如果 WA 了，TLE 了，RE 了，不要担心，多调试一下，多试几组数据，AC 就在眼前。 尽管你的代码没有AC，但并不意味着你这道题没有分数。你的代码可能会通过部分测试点。那么你就可以获得该部分测试点的分数。 比如下图，H题目通过了第1、2个测试点，那么就获得了这两个测试点的分数0.3333。剩余测试点由于种种原因TLE，因此没有通过测试点。 ","tags":[{"index":-1,"name":"教程","slug":"GWAaV2nvk","used":true,"link":"https://github.pansis.site/tag/GWAaV2nvk/"}],"title":"OJ指南","link":"https://github.pansis.site/post/oj-zhi-nan/","stats":{"text":"4 min read","time":223000,"words":1062,"minutes":4},"dateFormat":"2023-09-03"},{"content":"Clion与Dev-cpp优缺点 Clion 优点： 1、具有代码补全功能，加快写代码的速度。 2、具有更完善的代码检查和纠正功能，避免低级错误无法发现。 3、适合大型项目的编写。对于信息类的同学来说，提前接触更现代化的IDE有利于进行后续的软件开发。 4、调试功能更强大。 缺点： 1、安装配置麻烦，不能做到Dev-cpp的即装即用。 2、需要许可证激活（对于学生来说，可以通过教育认证免费使用）。 3、本课程考试可能只允许使用Dev-cpp或者codeblock。 Dev-cpp 优点： 1、即装即用。 2、原生支持单文件编译，适合OJ做题。 缺点： 1、缺少代码补全、代码检查、代码纠正功能，容易在低级错误中无法自拔。 2、IDE老旧，不适合当前的软件开发。 安装包地址 Dev-cpp是即装即用的C与C++的IDE，他自身带有C与C++的编译器。 安装包地址： https://bhpan.buaa.edu.cn/link/AA15DA77D790C24F7FAECA2311C791D444 有效期限：2027-10-08 18:26 下载好的安装包，按照安装程序提示安装。 Hello，world程序 点击 “文件-新建源代码”，得到一个新的源代码文件。 写一份hello，world的代码 点击编译运行 在保存为界面中选择自己存放代码的文件夹，同时保存类型务必选择c文件 保存后，程序完成编译并会运行。 出现上述界面就代表着你已经成功的写出了最经典的程序 hello，world。 调试 转载改编于Dev C++ 调试功能详细总结 - 知乎 (zhihu.com) **对于编程学习，可以说调试能力是最重要的技能，没有之一！**所谓调试程序，就是控制编译器一行一行地执行代码，过程中可以清楚看到每个变量值的变化情况、函数的调用过程等，进而发现程序中隐藏的错误或者低效的代码。 以下边的代码为例子: #include &lt;stdio.h&gt; int main() { int n, cnt = 0; scanf(&quot;%d&quot;, &amp;n); for(int j = 0; j &lt; n; j ++) { cnt = cnt + 1; } printf(&quot;%d&quot;, cnt); return 0; } 添加断点，这是调试功能的核心，下边是几个步骤： 1.左键添加断点，主要是在比较容易出错，或者有怀疑点的位置 2.添加变量查看，可以实时看到代码中变量的值，非常的直观 3.点击②后，会有③窗口，添加代码中变量名即可 4.输入回车后，在左侧④调试窗口就会出现变量的实时值 点击调试或者按F5启动调试功能，会出现黑色的控制台窗口 此时根据代码，代码会走到第六行scanf等待你的键盘输入 这里我们输入5回车 断点行会变为蓝色，意味着此时代码运行到第8行并停止 左侧变量n由于执存储了键盘的输入变为了5，cnt由于还没有执行仍为0 点击下一步，代码会继续执行一行，根据逻辑会运行到for循环入口，cnt由于执行了一次变为了1，而n由于没有操作所以还是5 另外几个调试功能的作用（都可以动手实践下）： 下一步：控制编译器向下执行一行语句； 单步进入：功能和“下一步”按钮类似，不同之处在于，当程序中调用某个自定义的函数时，此按钮可以进入到函数内部，继续调试函数内部的代码，而 “下一步” 按钮不会； 跳过：继续执行程序，遇到下一个断点暂停执行； 停止执行：停止调试程序。 当最后执行完for循环，printf函数会输出结果5到控制台，左侧cnt值也增长到了5 这就是一次相对比较简单的调试了，大家可以亲自动手实践下，可能会到各种奇怪的问题，都非常正常，开发本就是解决问题的一个过程，这个调试虽然简单，但是基本的原理就是这样了，对于很多项目中都可以用到。 ","tags":[],"title":"Dev-cpp新手教程","link":"https://github.pansis.site/post/dev-cpp-an-zhuang/","stats":{"text":"4 min read","time":234000,"words":1086,"minutes":4},"dateFormat":"2023-09-03"},{"content":"Clion与Dev-cpp优缺点 Clion 优点： 1、具有代码补全功能，加快写代码的速度。 2、具有更完善的代码检查和纠正功能，避免低级错误无法发现。 3、适合大型项目的编写。对于信息类的同学来说，提前接触更现代化的IDE有利于进行后续的软件开发。 4、调试功能更强大。 缺点： 1、安装配置麻烦，不能做到Dev-cpp的即装即用。 2、需要许可证激活（对于学生来说，可以通过教育认证免费使用）（但北航邮箱已无法认证，只能破解）。 3、本课程考试可能只允许使用Dev-cpp或者codeblock。 Dev-cpp 优点： 1、即装即用。 2、原生支持单文件编译，适合OJ做题。 缺点： 1、缺少代码补全、代码检查、代码纠正功能，容易在低级错误中无法自拔。 2、IDE老旧，不适合当前的软件开发。 Clion安装与认证 本章内容由于北航邮箱被禁用认证而失效，目前只能采用破解方法使用clion。 破解和安装方法可见该链接 【2023】Clion2023.3最新破解教程+永久激活码工具 - 哔哩哔哩 (bilibili.com) 破解安装后直接跳转到下一章对clion进行基本设置。 北航邮箱注册 访问北航邮箱网址，完成北航邮箱注册。 网址：北航邮箱 一个北航邮箱会有两个邮箱账号，一个是“学号@buaa.edu.cn”,另一个是“邮箱名@buaa.edu.cn”（邮箱名为注册时填写的邮箱账号） JetBrains账户注册与学生认证 打开JetBrains账户登录页面JetBrains Account 输入北航邮箱或其他个人邮箱，点击注册后在个人邮箱内点击链接完成注册。 注册后在跳转的界面JetBrains Account进行学生认证 进入北航邮箱 北航邮箱，点击认证链接。 滑到底，点击同意 至此，我们就拥有了一个有学生许可证的JetBrains账户，用于后续Clion的激活。 Clion安装与激活 进入下载官网，根据系统选择安装包。 下载 CLion：用于 C/C++ 开发的智能跨平台 IDE (jetbrains.com) 下载完成后按照提示安装即可。安装设置可以参考下图，也可根据习惯选择。 安装完成后，打开Clion，在认证界面登录账户。 登陆后，在Clion中点击activate完成认证。 Clion基本设置 Dev-cpp安装 Dev-cpp是即装即用的C与C++的IDE，他自身带有C与C++的编译器。而早期版本的Clion并不内置编译器，因此可以通过先安装Dev-cpp来完成编译器安装。 （高版本Clion已内置编译器，所以此步骤非必需。但Dev-cpp作为即装即用的IDE，建议也安装） 安装包地址：https://bhpan.buaa.edu.cn/link/AA15DA77D790C24F7FAECA2311C791D444 有效期限：2027-10-08 18:26 新建项目 新建项目，请按照下面指示创建。 （C99是C语言1999年发布的标准，每一版的标准会略有差异，感兴趣的同学可以自行研究。） （个人经验判断，C99可能比较兼容咱们的OJ平台） （正常情况下，Clion能够自动搜索到编译器。如果显示的并非上图界面，请将Toolset路径设置为dev-cpp根目录的路径，Clion将尝试在该文件夹下搜索编译器） 在新建项目后，运行hello world程序。如果编译运行正常，则项目创建成功。 创建其他c文件 在c语言课程中，一个比赛我们会编写多个代码，同时也需要保存一些代码，因此需要创建多个c文件来存储和运行。 在指定路径下右键，选择File。 输入指定文件名，注意要输入拓展名.c作为结尾。随后回车。 Tips （1）上述所展示的编译是单文件编译，这种编译方式适合于OJ刷题。但真正的项目往往需要多个c文件共同编译，这就涉及到多文件编译，感兴趣的同学可以自行搜索研究。 （Clion项目默认创建的CMakelist.txt就是CMake编译所需要的文件） （虽然我们不需要用CMake编译，但请勿删CMakelist.txt和cmake-build-debug文件夹） （2）单文件编译每次会在主目录生成编译后的exe程序，可以定期清理。 Clion中文问题 IDE中文插件 安装完成后，根据提示重启IDE即可。 程序运行中文乱码解决 对于c语言课程来说，OJ不会涉及到中文输入输出，因此Clion中文乱码问题可以不用解决。 按住 Ctrl+Shift+Alt+/ ，选中Registry... 然后取消run.processes.with.pty 然后就可以了~ 转载于Clion 中 的乱码问题正确解决方案（来自官方技术支持） - 知乎 (zhihu.com) Clion debug（调试） 实例演示 在代码中随便编辑一些代码，代码中需要有变量赋值操作，用于观察debug模式,参考代码如下所示 #include &lt;stdio.h&gt; int main() { int i = 0; while (i &lt; 5) { i++; } return 0; } 编写完代码之后，在需要暂停的代码处进行断点，标注方法是用鼠标点击行号右侧旁边，会出现小红点，如下图所示。 接下来就可以使用debug模式观察变量的数据了，在编辑器的右侧上方有一个绿色虫子的图标，点击此图标就进入了debug模式。 调试器用户界面 当调试器命中断点时，究竟会发生什么呢？ 除了暂停执行外，您还会看到以下调试工具窗口： 这里有很多内容，所以我们首先讨论两个主要标签（顶级标签，可以这么说）。 它们是： 调试器 (Debugger) — 这实际上显示了我们将在稍后讨论的各种调试选项。 控制台 (Console) — 如果您的应用程序有任何输出，那么该区域显示命令行输出。 在这些选项卡的右边有一系列按钮，这些按钮使您可以在调试的代码中导航。 其中包括让您： 显示执行点 (Show Execution Point) (Alt+F10 )， 逐函数执行 (Step Over) (F8 )， 逐行执行 (Step Into) (F7 )， 强制逐行执行 (Force Step Into) (Alt+Shift+F7 )， 跳出 (Step Out) (Shift+F8 )， 和运行到光标 (Run to Cursor) (Alt+F9 )。 在堆栈框架右侧有两个选项卡：变量 (Variables) 和 GDB (LLDB)。 我们将稍后谈论变量；就 GDB (LLDB) 而言，该选项卡基本上显示了 GDB (LLDB) 自身的命令行输出，因为那是用于调试我们应用的调试器。 变量 最简单的查看变量状态的方法当然是在代码中。 只需将鼠标悬停在存疑的变量上，就会看到如下内容： 当然，您可以在 Debug 工具窗口更全面地了解变量。 对于任何一个变量，我们可以展开树以查看其内容： 转载于 Clion Debug模式使用实践 - 知乎 (zhihu.com) 怎么使用 CLion 的调试（Debug） - 知乎 (zhihu.com) ","tags":[],"title":"Clion新手教程","link":"https://github.pansis.site/post/clion-jiao-cheng/","stats":{"text":"7 min read","time":396000,"words":1820,"minutes":7},"dateFormat":"2023-09-02"},{"content":"以下Python环境的配置，与Pycharm和Anaconda紧密配合。 Pycharm安装 进入官网https://www.jetbrains.com/pycharm/ 在下载界面中下载社区版 下载完成后，根据安装提示一路安装即可。安装好的pycharm先不要启动。 如果对Pycharm的英文界面不熟悉，可以查看教程最后的安装中文插件部分。 Anaconda安装 进入官网https://www.anaconda.com/download/#windows 下载结束后，打开安装包安装 单击【Next】， 单击【I Agree】， “Just Me”是只供当前用户使用，“All Users”是供使用这台电脑的所有用户使用，是权限问题，无论 如何选择，对安装空间影响不大， 一般选择“All Users”，单击【Next】， 选择安装路径，此处注意， 安装路径不可以出现：中文、空格、括号等特殊字符，同时不建议放置在C盘，后续环境配置可能没有权限创建环境，请务必记住这个安装路径，后续配置将会用到。 单击【Next】， 单击【Next】， Python环境配置 首先打开Pycharm，在开始页面创建一个项目。 请根据蓝框配置选择conda和python3.8（推荐）版本，红框位置为项目和环境存放路径，可自定义。 conda位置选择anaconda安装路径下的_conda.exe (如果你直接安装在下图中的默认路径中，无法找到programdata文件夹，可以直接复制下面的路径) 点击create后，IDE将自动创建环境。 在新打开的界面中，运行自动创建的脚本，若下方区域正常输出，则环境创建完成。 创建环境中，如果出现因为权限不足导致创建失败的问题。这是因为将conda安装在了C盘，C盘的读写需要更高的权限。请关闭pycharm，以管理员身份运行。 其他操作 Python环境操作 打开设置，在下图位置的解释器设置中，可以进行环境创建、切换与删除。 IDE中文插件 打开Pycharm的设置 选择Plugins，搜索Chinese 在搜索出的插件中，选择&quot;Chinese（Simplified）Language Pack/中文语言包&quot;，点击“Install” 重新启动一下Pycharm即可 转载于PyCharm 汉化插件 (taodudu.cc) 安装Python库 当你想要安装库时，点击界面中的该图标 在该命令行下使用pip命令安装库。 比如安装OpenCV，输入pip install opencv-python，回车即可自动安装。 注意：这种方法并不是标准方法，建议大家自行学习conda的一些命令，在cmd控制台尝试安装库，这样会对Python环境有更深的理解。 ","tags":[{"index":-1,"name":"Python与OpenCV","slug":"L7r9STb75","used":true,"link":"https://github.pansis.site/tag/L7r9STb75/"}],"title":"Python-Anaconda环境配置","link":"https://github.pansis.site/post/python-anaconda-huan-jing-pei-zhi/","stats":{"text":"3 min read","time":146000,"words":673,"minutes":3},"dateFormat":"2023-06-27"}]}
